/// <summary> /// 스도쿠 보드의 빈 셀을 스도쿠 기본 규칙에 따라 해결합니다. /// </summary> /// <param name="sudokuBoard">9*9 가변 배열</param> /// <returns>스도쿠가 해결이 가능한지를 반환합니다.</returns> public bool SolveSudoku(byte[][] sudokuBoard) { if (!SudokuUtils.IsSudokuBoardValid(sudokuBoard)) { throw new ArgumentException(InvalidSudokuBoardMessage); } return(SolveSudokuRec(sudokuBoard)); }
public void HardSudokus() { var solver = new SudokuSolver(); foreach (var s in sudokus) { var solved = solver.solve(s); Assert.NotNull(solved); Assert.True(SudokuUtils.isValidSudokuSolution(solved, s)); } }
/// <summary> /// 플레이어가 셀이 저장하려는 값의 유효성을 검사합니다. /// </summary> private void DataGridSudoku_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e) { var cell = e.EditingElement as TextBox; byte cellValue = 0; var row = (byte)e.Row.GetIndex(); var col = (byte)e.Column.DisplayIndex; if (cell.Text != string.Empty) { bool isCellValueValid = false; if (byte.TryParse(cell.Text, out cellValue)) { if (0 < cellValue && cellValue < 10) { var sudokuBoard = SudokuUtils.GenerateSudokuBoardFromGrid(currentSudokuGrid.ToArray()); if (sudokuSolver.IsNewCellValid(sudokuBoard, row, col, cellValue)) { isCellValueValid = true; } playerActions.Push(new FillCellAction(row, col, cellValue)); } } if (isCellValueValid) { if (IsUnvalidCellValueAdded) { IsUnvalidCellValueAdded = false; UnvalidCellValueRemoved?.Invoke(sender, e); } } else { IsUnvalidCellValueAdded = true; UnvalidCellValueAdded?.Invoke(sender, e); e.Cancel = true; cell.BorderBrush = Brushes.Red; cell.BorderThickness = new Thickness(2); } } else { IsUnvalidCellValueAdded = false; UnvalidCellValueRemoved?.Invoke(sender, e); e.Cancel = true; currentSudokuGrid[row][col].Value = null; cell.BorderBrush = Brushes.Black; cell.BorderThickness = new Thickness(0); } }
/// <summary> /// 해결하기 위한 유효한 스도쿠 보드를 생성합니다. /// </summary> /// <param name="sudokuDifficulty">생성된 스도쿠의 난이도</param> public SudokuRow[] GenerateSudoku(SudokuDifficultyType sudokuDifficulty) { sudokuTransformer.ShuffleSudoku(generatedSudokuBoard); for (int i = 0; i < 9; i++) { generatedSudokuBoard[i].CopyTo(sudokuBoardForPlayer[i], 0); } sudokuTransformer.EraseCells(sudokuBoardForPlayer, sudokuDifficulty); return(SudokuUtils.GenerateSudokuGridFromBoard(sudokuBoardForPlayer)); }
/// <summary> /// 스도쿠 보드 (Grid)에 마지막으로 생성된 스도쿠를 채웁니다. /// </summary> public void RestartSudoku() { playerActions.Push(new RestartAction(currentSudokuGrid.ToArray())); var restartedSudokuGrid = new SudokuRow[9]; SudokuUtils.CopySudokuGrid(initialSudokuGrid, restartedSudokuGrid); UpdateSudokuGridItems(restartedSudokuGrid); IsUnvalidCellValueAdded = false; InitiallyFilledSudokuCellsCount = SudokuUtils.GetFilledSudokuCellsCount(initialSudokuGrid, false); }
/// <summary> /// 스도쿠 보드 (Grid)에 새로운 스도쿠를 채웁니다. /// </summary> public void GenerateAndPopulateSudoku() { var newSudokuGrid = sudokuGenerator.GenerateSudoku(SudokuDifficulty); initialSudokuGrid = new SudokuRow[9]; SudokuUtils.CopySudokuGrid(newSudokuGrid, initialSudokuGrid); InitiallyFilledSudokuCellsCount = SudokuUtils.GetFilledSudokuCellsCount(initialSudokuGrid, false); UpdateSudokuGridItems(newSudokuGrid); IsUnvalidCellValueAdded = false; playerActions = new Stack <IPlayerAction>(); undonePlayerActions = new Stack <IPlayerAction>(); }
private SudokuRow[] SolveSudoku(SudokuRow[] sudokuGrid) { var sudokuBoard = SudokuUtils.GenerateSudokuBoardFromGrid(sudokuGrid); bool isSolvable = this.sudokuSolver.SolveSudoku(sudokuBoard); if (isSolvable) { var solvedSudokuGrid = SudokuUtils.GenerateSudokuGridFromBoard(sudokuBoard); return(solvedSudokuGrid); } else { return(null); } }
/// <summary> /// 섞으면서 유효한 스도쿠로 부터 다른 스도쿠를 생성합니다. /// </summary> /// <param name="sudokuBoard">9*9 가변 배열</param> public void ShuffleSudoku(byte[][] sudokuBoard) { if (!SudokuUtils.IsSudokuBoardValid(sudokuBoard)) { throw new ArgumentException(InvalidSudokuBoardMessage); } // 최소 20번, 최대 31번 중의 값만큼 스도쿠를 변환 int transformationsToPerform = random.Next(20, 31); // 시스템 Random() 함수로 선택된 값만큼 0부터 1씩 증가하며 For문을 돌린다. for (int transformationsCount = 0; transformationsCount < transformationsToPerform; transformationsCount++) { var transformationType = (SudokuBoardTransformationType)random.Next(0, 6); TransformSudokuBoard(sudokuBoard, transformationType); } }
/// <summary> /// 플레이어가 채운 셀을 기반으로 스도쿠의 현재 진행 상황을 반환합니다. /// </summary> public double GetProgress() { if (InitiallyFilledSudokuCellsCount == MaxFilledSudokuCellsCount) { return(100); } int playerFilledCells = SudokuUtils.GetFilledSudokuCellsCount(currentSudokuGrid.ToArray(), true); double progress = playerFilledCells / (double)(MaxFilledSudokuCellsCount - InitiallyFilledSudokuCellsCount) * 100; if (progress == 100) { SudokuSolved?.Invoke(this, new EventArgs()); } return(progress); }
/// <summary> /// 플레이어가 마지막으로 수행한 동작을 취소합니다. /// 새로운 스도쿠가 생성되면 플레이어 액션이 다시 시작됩니다. /// </summary> /// <returns>취소할 플레이어 액션이 있는지를 반환합니다.</returns> public bool UndoPlayerAction() { if (IsUnvalidCellValueAdded) { return(false); } if (playerActions.Count > 0) { var playerAction = playerActions.Pop(); if (playerAction.PlayerActionType == PlayerActionType.FillCell) { undonePlayerActions.Push(playerAction); var fillCellDec = playerAction as FillCellAction; currentSudokuGrid[fillCellDec.Row][fillCellDec.Column].Value = null; RefreshSudokuGridItems(); } else if (playerAction.PlayerActionType == PlayerActionType.Restart) { undonePlayerActions.Push(new RestartAction(currentSudokuGrid.ToArray())); var restartDec = playerAction as RestartAction; UpdateSudokuGridItems(restartDec.SudokuGridBeforeAction); } else if (playerAction.PlayerActionType == PlayerActionType.Solve) { undonePlayerActions.Push(new SolveAction(currentSudokuGrid.ToArray())); var solveDec = playerAction as SolveAction; UpdateSudokuGridItems(solveDec.SudokuGridBeforeAction); InitiallyFilledSudokuCellsCount = SudokuUtils.GetFilledSudokuCellsCount(initialSudokuGrid, false); } return(true); } return(false); }
/// <summary> /// 새로운 셀이 스도쿠 기본 규칙을 따르는지, 유효한지를 검사합니다. /// </summary> /// <param name="sudokuBoard">9x9 가변 배열</param> /// <param name="row">새로운 셀의 행</param> /// <param name="column">새로운 셀의 행</param> /// <param name="value">새로운 셀의 행</param> /// <returns>새로운 셀이 유효한지를 반환합니다.</returns> public bool IsNewCellValid(byte[][] sudokuBoard, byte row, byte column, byte value) { if (!SudokuUtils.IsSudokuBoardValid(sudokuBoard)) { throw new ArgumentException(InvalidSudokuBoardMessage); } if ((row < 0 || row > 8) || (column < 0 || column > 8)) { throw new IndexOutOfRangeException("행과 열의 값은 반드시 0부터 8사이 이여야 합니다!"); } for (int i = 0; i < 9; i++) { // 행 검사 if (sudokuBoard[row][i] == value && i != column) { return(false); } // 열 검사 if (sudokuBoard[i][column] == value && i != row) { return(false); } // 3*3 보드 검사 int groupRow = row / 3 * 3 + i / 3; int groupCol = column / 3 * 3 + i % 3; if (sudokuBoard[groupRow][groupCol] == value && (groupRow != row || groupCol != column)) { return(false); } } // 유효하다면 return(true); }
/// <summary> /// 유효한 스도쿠에서 난이도에 알맞게 셀을 삭제하여 플레이어가 해결할 수 있도록 합니다. /// </summary> /// <param name="sudokuBoard">9*9 가변 배열</param> public void EraseCells(byte[][] sudokuBoard, SudokuDifficultyType sudokuDifficulty) { if (!SudokuUtils.IsSudokuBoardValid(sudokuBoard)) { throw new ArgumentException(InvalidSudokuBoardMessage); } // 지울 셀의 개수를 0으로 초기화 int cellsToErase = 0; // 지울 셀의 개수를 난이도에 따라 지정 if (sudokuDifficulty == SudokuDifficultyType.데모) { cellsToErase = CellsToEraseOnDemo; } if (sudokuDifficulty == SudokuDifficultyType.Easy) { cellsToErase = CellsToEraseOnEasyDifficulty; } else if (sudokuDifficulty == SudokuDifficultyType.Medium) { cellsToErase = CellsToEraseOnMediumDifficulty; } else if (sudokuDifficulty == SudokuDifficultyType.Hard) { cellsToErase = CellsToEraseOnHardDifficulty; } else if (sudokuDifficulty == SudokuDifficultyType.Expert) { cellsToErase = CellsToEraseOnImpossibleDifficulty; } else if (sudokuDifficulty == SudokuDifficultyType.Custom) { cellsToErase = CustomMode; } else { cellsToErase = 20; // 오류 발생시 빈칸 20개 뚫기 (Demo 와 Easy 사이 난이도) } // 셀 지우는 방법: // 1. 행에서 시스템의 Random() 함수로 선택된 셀을 지운다. // 2. 반 대각선 (Minor Diagonal) 에 있는 셀을 지운다. // 3. 반복한다. while (cellsToErase > 0) { // 0 부터 8 까지의 행, 1씩 증가 for (int row = 0; row < 9; row++) { // 최소 0, 최대 9 의 열 중 하나를 선택 (시스탬이 Random() 함수로 선택) int col = random.Next(0, 9); if (sudokuBoard[row][col] != 0) // 선택받은 (?) sudokuBoard[행][열] 이 0이 아니라면 (빈칸이 아니라면) { sudokuBoard[row][col] = 0; // 빈칸으로 전환 cellsToErase--; // 지울 셀의 개수 - 1 } if (cellsToErase <= 0) // 지울 셀의 개수가 0 이하가 되면 { return; // 멈춤 } int oppositeRow = 9 - col - 1; // 반대편 행 선택 int oppositeCol = 9 - row - 1; // 반대편 열 선택 if (sudokuBoard[oppositeRow][oppositeCol] != 0) // sudokuBoard[반대편 행][반대편 열] 이 0이 아니라면 (빈칸이 아니라면) { sudokuBoard[oppositeRow][oppositeCol] = 0; // 빈칸으로 전환 cellsToErase--; // 지울 셀의 개수 - 1 } if (cellsToErase <= 0) // 지울 셀의 개수가 0 이하가 되면 { return; // 멈춤 } } } }
public void TestValidSolutionChecker() { Assert.True(SudokuUtils.isValidSudokuSolution(new Sudoku(solvedsudoku), solvedsudoku)); }
public override string ToString() { return(SudokuUtils.gridToString(grid)); }