using System; using System.Collections.Generic; using System.Text; namespace Sudoku { public class Board { public readonly Cell[,] Cells; private readonly int _emptyCellsCount; public static Board CreateInitialBoard(int[][] boardData) => new Board(boardData); private Board(IReadOnlyList boardData) { _emptyCellsCount = 9 * 9; Cells = new Cell[9,9]; for (var row = 0; row < 9; row++) for (var col = 0; col < 9; col++) Cell(row, col) = new Cell(boardData[row][col]); // update all board with impossible options based on the initial numbers for (var row = 0; row < 9; row++) for (var col = 0; col < 9; col++) { var cell = Cell(row, col); if (cell.IsFilled) // update other affected by filled { _emptyCellsCount--; UpdateCellsAffectedBy(row, col, cell.Number); } } } /// /// MakeUpdatedCopy constructor /// private Board(Cell[,] cells, int activeRow, int activeCol, int activeNumber, int emptyCellsCount) { Cells = cells; _emptyCellsCount = emptyCellsCount; Cell(activeRow, activeCol).Number = activeNumber; UpdateCellsAffectedBy(activeRow, activeCol, activeNumber); } public bool IsSolved => _emptyCellsCount == 0; /// /// Makes a copy of board with the next possible number set into the active cell /// /// public Board TryNextNumber() { GetCellToTry(out var activeRow, out var activeCol); var number = Cell(activeRow, activeCol).PopNextNumber(); return number != NumberEx.Unknown ? new Board(CopyCells(), activeRow, activeCol, number, _emptyCellsCount - 1) : null; // we tried all numbers and all branches finished by dead end } private Cell[,] CopyCells() { var cellsCopy = new Cell[9, 9]; for (var row = 0; row < 9; row++) for (var col = 0; col < 9; col++) cellsCopy[row, col] = Cell(row, col).Copy(); return cellsCopy; } private void UpdateCellsAffectedBy(int sourceRow, int sourceCol, int number) { var squareBeginRow = (sourceRow / 3) * 3; var squareBeginCol = (sourceCol / 3) * 3; var squareEndRow = squareBeginRow + 3; var squareEndCol = squareBeginCol + 3; for (var row = 0; row < 9; row++) for (var col = 0; col < 9; col++) { if (row == sourceRow || col == sourceCol || ((squareBeginRow <= row && row < squareEndRow) && (squareBeginCol <= col && col < squareEndCol))) { if (!Cell(row, col).IsFilled) Cell(row, col).RemoveOption(number); } } } private ref Cell Cell(int row, int col) => ref Cells[row, col]; /// /// Now it returns the first empty cell, it can be optimized /// private void GetCellToTry(out int row, out int col) { for (row = 0; row < 9; row++) for (col = 0; col < 9; col++) if (!Cell(row, col).IsFilled) return; throw new InvalidOperationException(); } /// /// For debug purpose /// public override string ToString() { var sb = new StringBuilder(); for (var row = 0; row < 9; row++) { for (var col = 0; col < 9; col++) { var cell = Cell(row, col); sb.Append(cell.Number); sb.Append(", "); } sb.AppendLine(); } return sb.ToString(); } } }