/// <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));
        }
예제 #2
0
    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));
        }
    }
예제 #3
0
        /// <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));
        }
예제 #5
0
        /// <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);
        }
예제 #6
0
        /// <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>();
        }
예제 #7
0
        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);
            }
        }
예제 #8
0
        /// <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);
            }
        }
예제 #9
0
        /// <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);
        }
예제 #10
0
        /// <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);
        }
예제 #12
0
        /// <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;                         // 멈춤
                    }
                }
            }
        }
예제 #13
0
 public void TestValidSolutionChecker()
 {
     Assert.True(SudokuUtils.isValidSudokuSolution(new Sudoku(solvedsudoku), solvedsudoku));
 }
예제 #14
0
 public override string ToString()
 {
     return(SudokuUtils.gridToString(grid));
 }