/// <summary> /// Generate sudoku to fill from full-filled sudoku /// </summary> /// <param name="sudoku"></param> /// <param name="notCheckedPositionsFromPrev"></param> /// <param name="difficultyPoints"></param> /// <param name="symmetry"></param> /// <param name="token">Cancellation token</param> /// <returns></returns> private static bool GenerateFromFull(Domain.Sudoku sudoku, IEnumerable <Tuple <int, int> > notCheckedPositionsFromPrev, int difficultyPoints, Enums.SymmetryType symmetry, CancellationToken token) { var notCheckedPositions = new List <Tuple <int, int> >(notCheckedPositionsFromPrev); while (notCheckedPositions.Count > 0) { token.ThrowIfCancellationRequested(); var removedCells = RemoveNext(sudoku, notCheckedPositions, symmetry); var solutions = Solver.GetTopNSolutions(sudoku, 2, Enums.TopType.Any); if (solutions.Count == 1 && solutions.First().DifficultyPoints > difficultyPoints) { sudoku.DifficultyPoints = solutions.First().DifficultyPoints; return(true); } if (solutions.Count > 1) { RecoverCells(sudoku, removedCells); return(false); } if (GenerateFromFull(sudoku, notCheckedPositions, difficultyPoints, symmetry, token)) { return(true); } RecoverCells(sudoku, removedCells); } return(false); }
/// <summary> /// Check if Sudoku remain consistent if we put number in specified location /// </summary> /// <param name="board">Board to check</param> /// <param name="row">row in board</param> /// <param name="column">column in board</param> /// <param name="value">value to put</param> /// <returns></returns> public static bool ConsistentIfPut(Domain.Sudoku board, int row, int column, int value) { for (var i = 0; i < Domain.Sudoku.BigSide; i++) { if (board[row, i] == value) { return(false); } if (board[i, column] == value) { return(false); } } var rowStart = row - row % Domain.Sudoku.SmallSide; var columnStart = column - column % Domain.Sudoku.SmallSide; for (var m = 0; m < Domain.Sudoku.SmallSide; m++) { for (var k = 0; k < Domain.Sudoku.SmallSide; k++) { if (board[rowStart + k, columnStart + m] == value) { return(false); } } } return(true); }
private void ClearAndSolve(Domain.Sudoku sudoku, int currentIndex, Enums.TopType type, int count) { _currentIterationsCount = 0; _solutions.Clear(); SolveBruteForce(sudoku, currentIndex, type, count); }
private static void RecoverCells(Domain.Sudoku sudoku, IList <Cell> cellsToRecover) { foreach (var cell in cellsToRecover) { sudoku[cell.Row, cell.Column] = cell.Value; } cellsToRecover.Clear(); }
/// <summary> /// Count number of solutions for given sudoku /// </summary> /// <param name="sudoku"></param> /// <returns></returns> public int CountSolutions(Domain.Sudoku sudoku) { if (sudoku == null) { throw new ArgumentNullException(nameof(sudoku)); } ClearAndSolve(sudoku.Copy(), 0, Enums.TopType.Any, int.MaxValue); return(_solutions.Count); }
/// <summary> /// Solve sudoku and return first available solution /// </summary> /// <param name="sudoku"></param> /// <returns></returns> public Domain.Sudoku SolveSudoku(Domain.Sudoku sudoku) { if (sudoku == null) { throw new ArgumentNullException(nameof(sudoku)); } ClearAndSolve(sudoku.Copy(), 0, Enums.TopType.Any, 1); return(_solutions.FirstOrDefault()); }
/// <summary> /// Save sudoku to local file named <see cref="LocalFile"/> /// </summary> /// <param name="sudoku"><see cref="Domain.Sudoku"/>></param> /// <returns></returns> public static async Task SaveSudokuAsync(Domain.Sudoku sudoku) { if (sudoku == null) { throw new NullReferenceException(nameof(sudoku)); } using (var fileStream = new FileStream(LocalFile, FileMode.Create)) using (var stream = new StreamWriter(fileStream)) { await stream.WriteAsync(sudoku.ToString()); } }
internal static Domain.Sudoku GenerateFull() { var sudoku = new Domain.Sudoku(); for (var i = 0; i < Domain.Sudoku.BigSide; i++) { for (var j = 0; j < Domain.Sudoku.BigSide; j++) { sudoku[i, j] = (i * Domain.Sudoku.SmallSide + i / Domain.Sudoku.SmallSide + j) % Domain.Sudoku.BigSide + 1; } } return(sudoku); }
private static List <Cell> RemoveNext(Domain.Sudoku sudoku, IList <Tuple <int, int> > notCheckedPositions, Enums.SymmetryType symmetry) { var randomPosition = Random.Next(0, notCheckedPositions.Count - 1); var currentCell = notCheckedPositions[randomPosition]; notCheckedPositions.RemoveAt(randomPosition); var row = currentCell.Item1; var column = currentCell.Item2; var removedValues = new List <Cell>() { new Cell(row, column, sudoku[row, column]) }; sudoku[row, column] = 0; if (symmetry == Enums.SymmetryType.Horizontal) { if (row == 4) { return(removedValues); } var symmetricRow = Domain.Sudoku.BigSide - 1 - row; removedValues.Add(new Cell(symmetricRow, column, sudoku[symmetricRow, column])); notCheckedPositions.Remove(Tuple.Create(symmetricRow, column)); sudoku[symmetricRow, column] = 0; } else if (symmetry == Enums.SymmetryType.Vertical) { if (column == 4) { return(removedValues); } var symmetricColumn = Domain.Sudoku.BigSide - 1 - column; removedValues.Add(new Cell(row, symmetricColumn, sudoku[row, symmetricColumn])); notCheckedPositions.Remove(Tuple.Create(row, symmetricColumn)); sudoku[row, symmetricColumn] = 0; } return(removedValues); }
/// <summary> /// Solve sudoku and return given number of solutions in given order /// </summary> /// <param name="sudoku"> Sudoku to solve </param> /// <param name="count"> Number of solutions to return </param> /// <param name="topType"> Sort order </param> /// <returns></returns> public List <Domain.Sudoku> GetTopNSolutions(Domain.Sudoku sudoku, int count, Enums.TopType topType) { if (sudoku == null) { throw new ArgumentNullException(nameof(sudoku)); } if (count < 0) { throw new Exception("Number of solutions can not be negative value"); } ClearAndSolve(sudoku.Copy(), 0, topType, count); return(topType == Enums.TopType.Hardest ? _solutions.Skip(Math.Max(0, _solutions.Count - count)).ToList() : _solutions.ToList()); }
private static bool CheckSudoku(Domain.Sudoku sudoku) { var vHash = new HashSet <int>(); var hHash = new HashSet <int>(); for (var i = 0; i < 9; i++) { for (var j = 0; j < 9; j++) { vHash.Clear(); hHash.Clear(); for (var k = 0; k < Domain.Sudoku.BigSide; k++) { if (vHash.Contains(sudoku[i, k])) { return(false); } vHash.Add(sudoku[i, k]); if (hHash.Contains(sudoku[k, j])) { return(false); } hHash.Add(sudoku[k, j]); } var rowStart = i - i % Domain.Sudoku.SmallSide; var columnStart = j - j % Domain.Sudoku.SmallSide; vHash.Clear(); for (var m = 0; m < Domain.Sudoku.SmallSide; m++) { for (var k = 0; k < Domain.Sudoku.SmallSide; k++) { if (vHash.Contains(sudoku[rowStart + k, columnStart + m])) { return(false); } vHash.Add(sudoku[rowStart + k, columnStart + m]); } } } } return(true); }
private static bool ValidateSubSquare(Domain.Sudoku board, int colStart, int rowStart) { var set = new bool[Domain.Sudoku.BigSide + 1]; for (var i = 0; i < Domain.Sudoku.SmallSide; i++) { for (var j = 0; j < Domain.Sudoku.SmallSide; j++) { if (board[rowStart + i, colStart + j] != 0 && set[board[rowStart + i, colStart + j]]) { return(false); } set[board[rowStart + i, colStart + j]] = true; } } return(true); }
private bool SolveBruteForce(Domain.Sudoku sudoku, int currentIndex, Enums.TopType type, int count) { while (true) { if (currentIndex == Domain.Sudoku.BigSide * Domain.Sudoku.BigSide) { var solutionBoard = sudoku.Copy(); solutionBoard.DifficultyPoints = _currentIterationsCount; _solutions.Add(solutionBoard); return(true); } else { var row = currentIndex / Domain.Sudoku.BigSide; var column = currentIndex % Domain.Sudoku.BigSide; if (sudoku[row, column] != 0) { currentIndex = currentIndex + 1; continue; } for (var i = 1; i <= Domain.Sudoku.BigSide; i++) { _currentIterationsCount++; if (!Validations.ConsistentIfPut(sudoku, row, column, i)) { continue; } sudoku[row, column] = i; if (SolveBruteForce(sudoku, currentIndex + 1, type, count) && (type == Enums.TopType.Any || type == Enums.TopType.Easiest) && _solutions.Count >= count) { return(true); } sudoku[row, column] = 0; } } return(false); } }
internal static void RunShuffle(Domain.Sudoku sudoku, int shufflesCount, CancellationToken token) { for (var i = 0; i < shufflesCount; i++) { token.ThrowIfCancellationRequested(); var firstParam = Random.Next(0, 2); var secondParam = Random.Next(0, 2); var thirdParam = Random.Next(0, 2); // to avoid exchange between one row/column while (secondParam == thirdParam) { thirdParam = Random.Next(0, 2); } var shuffleMethod = Random.Next(0, 4); switch (shuffleMethod) { case 0: sudoku.Transpose(); break; case 1: sudoku.SwapSmallRows(firstParam, secondParam, thirdParam); break; case 2: sudoku.SwapSmallColumns(firstParam, secondParam, thirdParam); break; case 3: sudoku.SwapBigRows(secondParam, thirdParam); break; case 4: sudoku.SwapBigColumns(secondParam, thirdParam); break; } } }
/// <summary> /// Import Sudoku from file /// </summary> /// <param name="path">Path to .txt file</param> /// <returns><see cref="Domain.Sudoku"/></returns> public static async Task <Domain.Sudoku> ReadFromFileAsync(string path) { if (!File.Exists(path)) { return(null); } var fileInfo = new FileInfo(path); var sudoku = new Domain.Sudoku(); var lineNumber = 0; using (var stream = new StreamReader(fileInfo.OpenRead())) { while (stream.Peek() >= 0) { var line = await stream.ReadLineAsync(); if (line.Length != Domain.Sudoku.BigSide) { throw new Exception($"Incorrect sudoku. Line {lineNumber} has length of {line.Length}"); } for (var column = 0; column < line.Length; column++) { if (int.TryParse(line[column].ToString(), out var num) && num != 0) { sudoku[lineNumber, column] = num; } else { sudoku[lineNumber, column] = 0; } } lineNumber++; } } return(sudoku); }
private static bool ValidateRowColumn(Domain.Sudoku board, int idx) { var rowSet = new bool[Domain.Sudoku.BigSide + 1]; var colSet = new bool[Domain.Sudoku.BigSide + 1]; for (var i = 0; i < Domain.Sudoku.BigSide; i++) { if (board[idx, i] != 0 && rowSet[board[idx, i]]) { return(false); } if (board[i, idx] != 0 && colSet[board[i, idx]]) { return(false); } rowSet[board[idx, i]] = true; colSet[board[i, idx]] = true; } return(true); }
/// <summary> /// Validate sudoku if it's consistent /// </summary> /// <param name="board"></param> /// <returns></returns> public static bool ValidateSudoku(Domain.Sudoku board) { for (var i = 0; i < Domain.Sudoku.BigSide; i++) { if (!ValidateRowColumn(board, i)) { return(false); } } for (var i = 0; i < Domain.Sudoku.BigSide; i += Domain.Sudoku.SmallSide) { for (var j = 0; j < Domain.Sudoku.BigSide; j += Domain.Sudoku.SmallSide) { if (!ValidateSubSquare(board, i, j)) { return(false); } } } return(true); }