Skip to content

Commit

Permalink
Made everything forced to be 9x9 sodukos
Browse files Browse the repository at this point in the history
  • Loading branch information
kris701 committed Apr 9, 2024
1 parent b26fb17 commit 4f1c44d
Show file tree
Hide file tree
Showing 21 changed files with 97 additions and 113 deletions.
1 change: 0 additions & 1 deletion Benchmarks/2x2.txt

This file was deleted.

5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@
This is a simple project about making an automatic Sudoku solver.
It can be run as a CLI tool, by running:

`dotnet run --project SudokuSolver --configuration Release -- --board {BOARD} --size {BOARDCELLSIZE} --solver {SOLVER}`
`dotnet run --project SudokuSolver --configuration Release -- --board {BOARD} --solver {SOLVER}`

Where:
* `BOARD` has to be a string of rows in the sudoku, where 0 represents a blank space
* `BOARDCELLSIZE` is the size of the board cells (3 in normal sudoku)
* `BOARD` has to be a string of rows in the sudoku (total of 81 numbers), where 0 represents a blank space
* `SOLVER` is the solver you want. The CLI tool will tell you what options are available

# Experiments
Expand Down
16 changes: 7 additions & 9 deletions SudokuSolver.Tests/BaseTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,26 @@ namespace SudokuSolver.Tests
public static class BaseTests
{
public static readonly TimeSpan Timeout = TimeSpan.FromSeconds(10);
private static readonly List<Benchmark> _benchmarks = new List<Benchmark>()
private static readonly List<string> _benchmarks = new List<string>()
{
new Benchmark("../../../../Benchmarks/11puzzles.txt", 3),
new Benchmark("../../../../Benchmarks/timan.txt", 3),
new Benchmark("../../../../Benchmarks/mypuzzles.txt", 3),
new Benchmark("../../../../Benchmarks/2x2.txt", 2),
new Benchmark("../../../../Benchmarks/95puzzles.txt", 3),
"../../../../Benchmarks/11puzzles.txt",
"../../../../Benchmarks/timan.txt",
"../../../../Benchmarks/mypuzzles.txt",
"../../../../Benchmarks/95puzzles.txt",
};

public static IEnumerable<object[]> TestCases()
{
foreach (var benchmark in _benchmarks)
{
foreach (var line in File.ReadAllLines(benchmark.File))
foreach (var line in File.ReadAllLines(benchmark))
{
var values = new List<byte>();
foreach (var c in line)
values.Add(byte.Parse($"{c}"));
yield return new object[] {
line,
values,
benchmark.BlockSize
values
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ public class BacktrackSolverTests

[TestMethod]
[DynamicData(nameof(Data), DynamicDataSourceType.Method)]
public void Can_Solve(string boardStr, List<byte> boardValues, byte blockSize)
public void Can_Solve(string boardStr, List<byte> boardValues)
{
// ARRANGE
var board = new SudokuBoard(boardValues.ToArray(), blockSize);
var board = new SudokuBoard(boardValues.ToArray());
var solver = new BacktrackSolver();
solver.Timeout = BaseTests.Timeout;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ namespace SudokuSolver.Tests.Solvers.BacktrackSolvers.Pruners
public class CertainsPrunerTests
{
[TestMethod]
[DataRow("600000803040700000000000000000504070300200000106000000020000050000080600000010000", 3, 30)]
[DataRow("052400000000070100000000000000802000300000600090500000106030000000000089700000000", 3, 21)]
public void Can_PruneCorrectly(string board, int boardSize, int expectedChange)
[DataRow("600000803040700000000000000000504070300200000106000000020000050000080600000010000", 30)]
[DataRow("052400000000070100000000000000802000300000600090500000106030000000000089700000000", 21)]
public void Can_PruneCorrectly(string board, int expectedChange)
{
// ARRANGE
var values = new List<byte>();
foreach (var c in board)
values.Add(byte.Parse($"{c}"));
var context = Preprocessor.Preprocess(new SudokuBoard(values.ToArray(), (byte)boardSize));
var context = Preprocessor.Preprocess(new SudokuBoard(values.ToArray()));
IPruner pruner1 = new CertainsPruner();
var preCount = context.Cardinalities.Sum(x => x.Possibilities);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ namespace SudokuSolver.Tests.Solvers.BacktrackSolvers.Pruners
public class HiddenPairPrunerTests
{
[TestMethod]
[DataRow("000000000904607000076804100309701080708000301051308702007502610005403208000000000", 3, 9)]
[DataRow("720408030080000047401076802810739000000851000000264080209680413340000008168943275", 3, 10)]
public void Can_PruneCorrectly(string board, int boardSize, int expectedChange)
[DataRow("000000000904607000076804100309701080708000301051308702007502610005403208000000000", 9)]
[DataRow("720408030080000047401076802810739000000851000000264080209680413340000008168943275", 10)]
public void Can_PruneCorrectly(string board, int expectedChange)
{
// ARRANGE
var values = new List<byte>();
foreach (var c in board)
values.Add(byte.Parse($"{c}"));
var context = Preprocessor.Preprocess(new SudokuBoard(values.ToArray(), (byte)boardSize));
var context = Preprocessor.Preprocess(new SudokuBoard(values.ToArray()));
IPruner pruner1 = new HiddenPairPruner();
var preCount = context.Cardinalities.Sum(x => x.Possibilities);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ namespace SudokuSolver.Tests.Solvers.BacktrackSolvers.Pruners
public class NakedPairPrunerTests
{
[TestMethod]
[DataRow("400000938032094100095300240370609004529001673604703090957008300003900400240030709", 3, 18)]
[DataRow("080090030030000069902063158020804590851907046394605870563040987200000015010050020", 3, 9)]
public void Can_PruneCorrectly(string board, int boardSize, int expectedChange)
[DataRow("400000938032094100095300240370609004529001673604703090957008300003900400240030709", 18)]
[DataRow("080090030030000069902063158020804590851907046394605870563040987200000015010050020", 9)]
public void Can_PruneCorrectly(string board, int expectedChange)
{
// ARRANGE
var values = new List<byte>();
foreach (var c in board)
values.Add(byte.Parse($"{c}"));
var context = Preprocessor.Preprocess(new SudokuBoard(values.ToArray(), (byte)boardSize));
var context = Preprocessor.Preprocess(new SudokuBoard(values.ToArray()));
IPruner pruner1 = new NakedPairPruner();
var preCount = context.Cardinalities.Sum(x => x.Possibilities);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ namespace SudokuSolver.Tests.Solvers.BacktrackSolvers.Pruners
public class NakedTripplePrunerTests
{
[TestMethod]
[DataRow("070408029002000004854020007008374200020000000003261700000093612200000403130642070", 3, 12)]
[DataRow("294513006600842319300697254000056000040080060000470000730164005900735001400928637", 3, 18)]
public void Can_PruneCorrectly(string board, int boardSize, int expectedChange)
[DataRow("070408029002000004854020007008374200020000000003261700000093612200000403130642070", 12)]
[DataRow("294513006600842319300697254000056000040080060000470000730164005900735001400928637", 18)]
public void Can_PruneCorrectly(string board, int expectedChange)
{
// ARRANGE
var values = new List<byte>();
foreach (var c in board)
values.Add(byte.Parse($"{c}"));
var context = Preprocessor.Preprocess(new SudokuBoard(values.ToArray(), (byte)boardSize));
var context = Preprocessor.Preprocess(new SudokuBoard(values.ToArray()));
IPruner pruner1 = new NakedTripplePruner();
var preCount = context.Cardinalities.Sum(x => x.Possibilities);

Expand Down
1 change: 0 additions & 1 deletion SudokuSolver.sln
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Benchmarks", "Benchmarks", "{069C67E9-B21A-4C20-9D18-562DA9326703}"
ProjectSection(SolutionItems) = preProject
Benchmarks\11puzzles.txt = Benchmarks\11puzzles.txt
Benchmarks\2x2.txt = Benchmarks\2x2.txt
Benchmarks\95puzzles.txt = Benchmarks\95puzzles.txt
Benchmarks\mypuzzles.txt = Benchmarks\mypuzzles.txt
Benchmarks\README.md = Benchmarks\README.md
Expand Down
2 changes: 1 addition & 1 deletion SudokuSolver/Models/CellAssignment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public bool IsLegal(SudokuBoard board)
}

public void Apply(SudokuBoard on) => on[X, Y] = Value;
public void UnApply(SudokuBoard on) => on[X, Y] = on.BlankNumber;
public void UnApply(SudokuBoard on) => on[X, Y] = SudokuBoard.BlankNumber;

public override string ToString() => $"[{X},{Y}:{Value}]";

Expand Down
23 changes: 7 additions & 16 deletions SudokuSolver/Models/SudokuBoard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ namespace SudokuSolver.Models
{
public class SudokuBoard
{
public byte BlockSize;
public byte BlankNumber = 0;
public byte Blocks;
public byte BoardSize;
public static readonly byte BlockSize = 3;
public static readonly byte BlankNumber = 0;
public static readonly byte Blocks = 3;
public static readonly byte BoardSize = 9;
public byte this[byte x, byte y]
{
get => _values[x + y * BoardSize];
Expand All @@ -32,13 +32,10 @@ public class SudokuBoard
private readonly unsafe bool[] _columns;
private readonly unsafe byte[] _blockRefs;

public SudokuBoard(byte[] values, byte blockSize)
public SudokuBoard(byte[] values)
{
BlockSize = blockSize;
BoardSize = (byte)(values.Length / (blockSize * blockSize));
_values = new byte[BoardSize * BoardSize];
_blockRefs = new byte[BoardSize * BoardSize];
Blocks = (byte)(BoardSize / blockSize);

_blocks = new bool[Blocks * Blocks * (BoardSize + 1)];
_rows = new bool[(BoardSize + 1) * (BoardSize + 1)];
Expand All @@ -54,12 +51,9 @@ public SudokuBoard(byte[] values, byte blockSize)
}
}

internal SudokuBoard(byte[] values, byte blockSize, byte size, byte blockCount, bool[] blocks, bool[] rows, bool[] columns, byte[] blockRefs)
internal SudokuBoard(byte[] values, bool[] blocks, bool[] rows, bool[] columns, byte[] blockRefs)
{
_values = values;
BlockSize = blockSize;
BoardSize = size;
Blocks = blockCount;
_blocks = blocks;
_rows = rows;
_columns = columns;
Expand Down Expand Up @@ -122,10 +116,7 @@ public unsafe SudokuBoard Copy()
Buffer.BlockCopy(_rows, 0, cpyRows, 0, (BoardSize + 1) * (BoardSize + 1) * sizeof(bool));
var cpyColumns = new bool[(BoardSize + 1) * (BoardSize + 1)];
Buffer.BlockCopy(_columns, 0, cpyColumns, 0, (BoardSize + 1) * (BoardSize + 1) * sizeof(bool));
return new SudokuBoard(cpyCells, BlockSize, BoardSize, Blocks, cpyBlocks, cpyRows, cpyColumns, _blockRefs)
{
BlankNumber = BlankNumber
};
return new SudokuBoard(cpyCells, cpyBlocks, cpyRows, cpyColumns, _blockRefs);
}

public override string ToString()
Expand Down
2 changes: 0 additions & 2 deletions SudokuSolver/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ public class Options
{
[Option("board", Required = true, HelpText = "The soduko board in a one dimentional array, row after row.")]
public string Board { get; set; } = "";
[Option("size", Required = true, HelpText = "The size of the blocks in the puzzle.")]
public int BlockSize { get; set; }
[Option("solver", Required = true, HelpText = "What solver to use.")]
public SolverOptions Solver { get; set; }

Expand Down
6 changes: 3 additions & 3 deletions SudokuSolver/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ public static void Run(Options opts)
foreach (var c in opts.Board)
values.Add(byte.Parse($"{c}"));

if (values.Count % opts.BlockSize != 0)
throw new Exception("Blocksize is not divisible with the board values!");
if (values.Count != 81)
throw new Exception("Board values must be exactly 81 characters long");

var board = new SudokuBoard(values.ToArray(), (byte)opts.BlockSize);
var board = new SudokuBoard(values.ToArray());

Console.WriteLine("Initial board:");
Console.WriteLine(board.ToString());
Expand Down
2 changes: 1 addition & 1 deletion SudokuSolver/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"profiles": {
"SudokuSolver": {
"commandName": "Project",
"commandLineArgs": "--board 000014000030000200070000000000900030601000000000000080200000104000050600000708000 --size 3 --solver BackTrack"
"commandLineArgs": "--board 000014000030000200070000000000900030601000000000000080200000104000050600000708000 --solver BackTrack"
}
}
}
20 changes: 10 additions & 10 deletions SudokuSolver/Solvers/BacktrackSolvers/Pruners/BasePruner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ public abstract class BasePruner : IPruner

internal List<CellAssignment> GetAssignmentsFromBlock(SearchContext context, byte blockX, byte blockY)
{
var fromX = blockX * context.Board.Blocks;
var toX = (blockX + 1) * context.Board.Blocks;
var fromY = blockY * context.Board.Blocks;
var toY = (blockY + 1) * context.Board.Blocks;
var fromX = blockX * SudokuBoard.Blocks;
var toX = (blockX + 1) * SudokuBoard.Blocks;
var fromY = blockY * SudokuBoard.Blocks;
var toY = (blockY + 1) * SudokuBoard.Blocks;
var cellPossibilities = new List<CellAssignment>();
for (int x = fromX; x < toX; x++)
for (int y = fromY; y < toY; y++)
Expand All @@ -28,7 +28,7 @@ internal List<CellAssignment> GetAssignmentsFromBlock(SearchContext context, byt
internal int PruneValueCandidatesFromRow(SearchContext context, List<CellAssignment> ignore, byte value)
{
var pruned = 0;
for (int x = 0; x < context.Board.BoardSize; x++)
for (int x = 0; x < SudokuBoard.BoardSize; x++)
if (!ignore.Any(z => z.X == x))
pruned += context.Candidates[x, ignore[0].Y].RemoveAll(v => v.Value == value);
return pruned;
Expand All @@ -37,7 +37,7 @@ internal int PruneValueCandidatesFromRow(SearchContext context, List<CellAssignm
internal int PruneValueCandidatesFromColumn(SearchContext context, List<CellAssignment> ignore, byte value)
{
var pruned = 0;
for (int y = 0; y < context.Board.BoardSize; y++)
for (int y = 0; y < SudokuBoard.BoardSize; y++)
if (!ignore.Any(z => z.Y == y))
pruned += context.Candidates[ignore[0].X, y].RemoveAll(v => v.Value == value);
return pruned;
Expand All @@ -46,10 +46,10 @@ internal int PruneValueCandidatesFromColumn(SearchContext context, List<CellAssi
internal int PruneValueCandidatesFromBlock(SearchContext context, byte blockX, byte blockY, List<CellAssignment> ignore, byte value)
{
var pruned = 0;
var fromX = blockX * context.Board.Blocks;
var toX = (blockX + 1) * context.Board.Blocks;
var fromY = blockY * context.Board.Blocks;
var toY = (blockY + 1) * context.Board.Blocks;
var fromX = blockX * SudokuBoard.Blocks;
var toX = (blockX + 1) * SudokuBoard.Blocks;
var fromY = blockY * SudokuBoard.Blocks;
var toY = (blockY + 1) * SudokuBoard.Blocks;
for (int x = fromX; x < toX; x++)
for (int y = fromY; y < toY; y++)
if (!ignore.Any(z => z.X == x && z.Y == y))
Expand Down
22 changes: 11 additions & 11 deletions SudokuSolver/Solvers/BacktrackSolvers/Pruners/CertainsPruner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,17 @@ public override bool Prune(SearchContext context)
private bool PruneCertains(SearchContext context)
{
var pruned = 0;
for (byte x = 0; x < context.Board.BoardSize; x++)
for (byte y = 0; y < context.Board.BoardSize; y++)
for (byte x = 0; x < SudokuBoard.BoardSize; x++)
for (byte y = 0; y < SudokuBoard.BoardSize; y++)
if (context.Candidates[x, y].Count == 1)
pruned += RemoveCandidate(context, context.Candidates[x, y][0]);

for (byte blockX = 0; blockX < context.Board.Blocks; blockX++)
for (byte blockX = 0; blockX < SudokuBoard.Blocks; blockX++)
{
for (byte blockY = 0; blockY < context.Board.Blocks; blockY++)
for (byte blockY = 0; blockY < SudokuBoard.Blocks; blockY++)
{
var cellPossibilities = GetAssignmentsFromBlock(context, blockX, blockY);
for (byte i = 1; i <= context.Board.BoardSize; i++)
for (byte i = 1; i <= SudokuBoard.BoardSize; i++)
if (cellPossibilities.Count(x => x.Value == i) == 1)
pruned += RemoveCandidate(context, cellPossibilities.First(x => x.Value == i));
}
Expand All @@ -47,17 +47,17 @@ private int RemoveCandidate(SearchContext context, CellAssignment cell)
context.Candidates[cell.X, cell.Y].Clear();
pruned++;

for (byte x2 = 0; x2 < context.Board.BoardSize; x2++)
for (byte x2 = 0; x2 < SudokuBoard.BoardSize; x2++)
pruned += context.Candidates[x2, cell.Y].RemoveAll(z => z.Value == cell.Value);
for (byte y2 = 0; y2 < context.Board.BoardSize; y2++)
for (byte y2 = 0; y2 < SudokuBoard.BoardSize; y2++)
pruned += context.Candidates[cell.X, y2].RemoveAll(z => z.Value == cell.Value);

var blockX = context.Board.BlockX(ref cell.X);
var blockY = context.Board.BlockY(ref cell.Y);
var fromX = blockX * context.Board.Blocks;
var toX = (blockX + 1) * context.Board.Blocks;
var fromY = blockY * context.Board.Blocks;
var toY = (blockY + 1) * context.Board.Blocks;
var fromX = blockX * SudokuBoard.Blocks;
var toX = (blockX + 1) * SudokuBoard.Blocks;
var fromY = blockY * SudokuBoard.Blocks;
var toY = (blockY + 1) * SudokuBoard.Blocks;

for (byte x = (byte)fromX; x < toX; x++)
for (byte y = (byte)fromY; y < toY; y++)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,17 @@ public override bool Prune(SearchContext context)
private bool PruneHiddenPairs(SearchContext context)
{
var pruned = 0;
for (byte blockX = 0; blockX < context.Board.Blocks; blockX++)
for (byte blockX = 0; blockX < SudokuBoard.Blocks; blockX++)
{
for (byte blockY = 0; blockY < context.Board.Blocks; blockY++)
for (byte blockY = 0; blockY < SudokuBoard.Blocks; blockY++)
{
var cellPossibilities = GetAssignmentsFromBlock(context, blockX, blockY);

for (byte i = 1; i <= context.Board.BoardSize; i++)
for (byte i = 1; i <= SudokuBoard.BoardSize; i++)
{
if (cellPossibilities.Count(x => x.Value == i) == 2)
{
for (int j = 1; j <= context.Board.BoardSize; j++)
for (int j = 1; j <= SudokuBoard.BoardSize; j++)
{
if (i == j)
continue;
Expand Down
Loading

0 comments on commit 4f1c44d

Please sign in to comment.