示例#1
0
    /// <summary>
    /// Method for solving the sudoku in a separate thread.
    /// </summary>
    public void Reworker()
    {
        TakePicture.Instance.solvedSudoku = ArrayHandling.Solve(TakePicture.Instance.unsolvedSudoku, ref TakePicture.Instance.success);
        if (TakePicture.Instance.success)
        {
            Popup.queued = new KeyValuePair <string, System.Action>(SolvedMessage, null);
        }

        TakePicture.Status = TakePicture.StatusDone; //triggers a check in TakePicture.Update to refill the sudoku with the new solution
        print("Recalculation done");
    }
示例#2
0
        public bool CheckForWin()
        {
            var ArrayHandler = new ArrayHandling <BoardType>();

            for (int i = 0; i <= 2; i++)
            {
                var  array = ArrayHandler.GetRow(_board, i);
                var  item  = array.FirstOrDefault();
                bool match = array.Skip(1).All(x => x == item && x != BoardType.UNFILLED);
                if (match)
                {
                    return(true);
                }
            }

            for (int i = 0; i <= 2; i++)
            {
                var  array = ArrayHandler.GetColumn(_board, i);
                var  item  = array.FirstOrDefault();
                bool match = array.Skip(1).All(x => x == item && x != BoardType.UNFILLED);
                if (match)
                {
                    return(true);
                }
            }

            if (_board[0, 0] != BoardType.UNFILLED && _board[0, 0] == _board[1, 1] && _board[1, 1] == _board[2, 2])
            {
                return(true);
            }
            if (_board[0, 2] != BoardType.UNFILLED && _board[0, 2] == _board[1, 1] && _board[1, 1] == _board[2, 0])
            {
                return(true);
            }


            return(false);
        }
示例#3
0
    /// <summary>
    /// Refreshes the sudoku view after a change is made.
    /// </summary>
    public void RecalculateSudoku()
    {
        print("Recalculating sudoku");
        TakePicture.debugText.enabled = true;
        TakePicture.Status            = "Solving Sudoku";
        Sudoku.transform.parent.gameObject.SetActive(false);

        TakePicture.Instance.unsolvedSudoku = new int[9, 9];
        TakePicture.Instance.solvedSudoku   = new int[9, 9];
        SudokuTile[] sudokuTiles = Sudoku.GetComponentsInChildren <SudokuTile>(true);

        //Reads the values from the sudoku tiles, see TakePicture.FillSudoku
        int x, y;

        for (int i = 0; i < 81; i++)
        {
            x = (i / 3) % 3 + (i / 27) * 3;
            y = 8 - (i % 3 + ((i / 9) % 3) * 3);
            if (sudokuTiles[i].Defined)
            {
                TakePicture.Instance.unsolvedSudoku[x, y] = sudokuTiles[i].Value;
            }
        }

        TakePicture.Instance.allZero = ArrayHandling.OnlyZeroes(TakePicture.Instance.unsolvedSudoku);

        //there should not be an active work thread at this point, but if there is, shut it down
        if (TakePicture.Instance.workThread != null && TakePicture.Instance.workThread.IsAlive)
        {
            TakePicture.Instance.workThread.Abort();
            TakePicture.Instance.workThread.Join(200);
        }
        //start reworker in a new thread
        TakePicture.Instance.workThread = new System.Threading.Thread(Reworker);
        TakePicture.Instance.workThread.Start();
    }
示例#4
0
    /// <summary>
    /// Uses a flood fill implementation to cut out an area of adjacently connected false elements.
    /// </summary>
    /// <param name="bitmap">The bitmap in which to look.</param>
    /// <param name="width">The width of the bitmap.</param>
    /// <param name="height">The height of the bitmap.</param>
    /// <param name="startIndex">The index of a false element from which to start the search.</param>
    /// <returns>The bitmap cutout.</returns>
    public static bool[,] ExtractDigit(bool[] bitmap, int width, int height, int startIndex)
    {
        int x = startIndex % width,
                                              xmin = x,
                                              xmax = x,
                                              y = startIndex / width,
                                              ymin = y,
                                              ymax = y;
        int   widthLimit = width / 9, heightLimit = height / 9;
        ulong next;

        Queue <ulong>   toVisit = new Queue <ulong>(512);
        HashSet <ulong> visited = new HashSet <ulong>();

        toVisit.Enqueue(PackULong(x, y));

        while (toVisit.Count > 0)
        {
            next = toVisit.Dequeue();
            if (visited.Contains(next))
            {
                continue;
            }
            visited.Add(next);

            UnpackULong(next, out x, out y);

            if (x >= 0 && y >= 0 && x < width && y < height &&
                !bitmap[x + y * width])
            {
                xmax = Mathf.Max(x, xmax);
                xmin = Mathf.Min(x, xmin);
                ymax = Mathf.Max(y, ymax);
                ymin = Mathf.Min(y, ymin);

                //make sure the selected area is not a significant portion of the image -
                //this would most likely mean we've accidentally started selecting the wireframe of the sudoku
                if (xmax - xmin > widthLimit || ymax - ymin > heightLimit)
                {
                    return new bool[, ] {
                               { false }
                    }
                }
                ;

                toVisit.Enqueue(PackULong(x + 1, y));
                toVisit.Enqueue(PackULong(x - 1, y));
                toVisit.Enqueue(PackULong(x, y + 1));
                toVisit.Enqueue(PackULong(x, y - 1));
            }
        }

        #if WRITE_IMAGES_TO_DISK
        TakePicture.colorq.Enqueue(new KeyValuePair <int, Color>(xmin + ymin * width, Color.cyan));
        TakePicture.colorq.Enqueue(new KeyValuePair <int, Color>(xmax + ymax * width, Color.cyan));
        #endif

        return(ArrayHandling.CutAndRaise(bitmap, width, height, xmin, ymin, xmax - xmin + 1, ymax - ymin + 1));
    }
    #endregion
}
示例#5
0
    private static int FailCounter     = 0;                     //counts how many times Identify() is called with a bitmap not resembling a digit
    /// <summary>
    /// Compares the cutout at a certain index to all stored digit bitmaps, and returns the best match.
    /// If the digit is different enough from its best match, it is inserted into the digit bitmap array.
    /// </summary>
    /// <param name="bitmap">The bitmap image of the whole sudoku.</param>
    /// <param name="width">The width of the bitmap.</param>
    /// <param name="height">The height of the bitmap.</param>
    /// <param name="index">The index at which the digit was found.</param>
    /// <param name="digits">
    /// A length 10 array of arbitrarily sized arrays of bitmaps, to be used when identifying the digits.
    /// This array can be modified by the function call, inserting and removing new bitmaps.
    /// </param>
    /// <param name="instantMatch">A 0-100 threshold of when two bitmaps should be considered identical.</param>
    /// <param name="maxBitmapsPerDigit">The maximum number of bitmaps each digit should be allowed to store.</param>
    /// <param name="digitout">The extracted digit bitmap, which can be inserted into identifiedBitmaps.</param>
    /// <returns>The digit which was found at the indicated position.</returns>
    public static int Identify(bool[] bitmap, int width, int height, int index, bool[][][,] digits, float instantMatch, int maxBitmapsPerDigit, ref bool[,] digitout)
    {
        #if WRITE_IMAGES_TO_DISK
        TakePicture.colorq.Enqueue(new KeyValuePair <int, Color>(index, Color.blue));
        #endif

        bool[,] digit = ExtractDigit(bitmap, width, height, index); //cut out the digit as a 2d bitmap
        digitout      = digit;

        TakePicture.Visualize(digit);

        //check if every digit is invalid, and if so, alert the user
        if (digit.Length <= 1 || SingleColor(digit))
        {
            if (++FailCounter == 81)
            {
                Popup.queued = new KeyValuePair <string, System.Action>(NoSudokuMessage, null);
                FailCounter  = 0;
            }
            MonoBehaviour.print(FailCounter);
            return(0);
        }

        //identify the digit by checking it against all stored bitmaps
        bool[][,] memoryArray;
        bool[,] digitStretched = new bool[0, 0], memoryStretched = new bool[0, 0];
        float matchpercent, bestPercent = 0;
        int   bestmatch = 0;

        #if CREATE_NEW_DEFAULT
        bestmatch = orderedDigits.Dequeue();
        #else
        for (int i = 1; i <= 9; i++)
        {
            memoryArray = digits[i];
            foreach (var memMap in memoryArray)
            {
                ArrayHandling.StretchToMatch(digit, memMap, out digitStretched, out memoryStretched);
                matchpercent = MatchPercent(digitStretched, memoryStretched);

                if (matchpercent >= instantMatch)
                {
                    return(i);
                }
                if (matchpercent > bestPercent)
                {
                    bestmatch   = i;
                    bestPercent = matchpercent;
                }
            }
        }
        #endif

        //save the bitmap if it is different enough
        bool[][,] bitmaps = digits[bestmatch] ?? new bool[0][, ];
        if (bitmaps.Length < maxBitmapsPerDigit)
        {
            MonoBehaviour.print("Adding digit " + bestmatch + " to storage");
            var temp = new bool[bitmaps.Length + 1][, ];
            for (int i = 0; i < bitmaps.Length; i++)
            {
                temp[i] = bitmaps[i];
            }

            temp[bitmaps.Length] = digit;
            digits[bestmatch]    = temp;
        }
        else
        {
            MonoBehaviour.print("Replacing random stored bitmap for digit " + bestmatch);
            bitmaps[Mathf.FloorToInt((float)random.NextDouble() * bitmaps.Length)] = digit;
        }

        return(bestmatch);
    }
示例#6
0
    /// <summary>
    /// Finds and identifies the digits, given the bitmap image and search settings.
    /// </summary>
    /// <param name="bitmap">The bitmap for which the search coordinates were calculated.</param>
    /// <param name="width">The width of the bitmap.</param>
    /// <param name="height">The height of the bitmap.</param>
    /// <param name="searchCoords">An array with 81 probable digit indexes. See FindDigitCoordinates().</param>
    /// <param name="searchRadius">
    /// The maximum distance from a calculated digit index
    /// to its actual match.Too high values may result in accidentally selecting
    /// the entire sudoku wireframe, while too low values may result in the algorithm
    /// not actually finding anything, assuming a zero.
    /// </param>
    /// <param name="digitBitmaps">
    /// A length 10 array of arbitrarily sized arrays of bitmaps, to be used when identifying the digits.
    /// This array can be modified by the function call, inserting and removing new bitmaps.
    /// </param>
    /// <param name="instantMatch">A 0-100 threshold of when two bitmaps should be considered identical.</param>
    /// <param name="maxBitmapsPerDigit">The maximum number of bitmaps each digit should be allowed to store.</param>
    /// <returns>A 9x9 sudoku where 0 represents an empty tile.</returns>
    public static int[,] GetSudoku(bool[] bitmap, int width, int height, int[] searchCoords, float searchRadius, bool[][][,] digitBitmaps, float instantMatch, int maxBitmapsPerDigit)
    {
        int[] result = new int[81];
        int   x, y, xscan = 0, yscan = 0;
        bool  breakout = false;

        bool[,] digit     = new bool[0, 0];
        identifiedBitmaps = new bool[81][, ];

        //try to find a hit in the desired area for each coordinate
        for (int i = 0; i < 81; i++)
        {
            if (!bitmap[searchCoords[i]])
            {
                result[i]            = Identify(bitmap, width, height, searchCoords[i], digitBitmaps, instantMatch, maxBitmapsPerDigit, ref digit);
                identifiedBitmaps[i] = digit;
                continue;
            }

            x = searchCoords[i] % width;
            y = searchCoords[i] / width;
            //Search in an octagonal pattern
            breakout = false;
            for (int j = 1; j <= searchRadius && !breakout; j++)
            {
                for (int k = 0; k < 8; k++)
                {
                    switch (k)
                    {
                    case 0:
                        xscan = x + j;
                        yscan = y;
                        break;

                    case 1:
                        xscan = x;
                        yscan = y + j;
                        break;

                    case 2:
                        xscan = x - j;
                        yscan = y;
                        break;

                    case 3:
                        xscan = x;
                        yscan = y - j;
                        break;

                    case 4:
                        xscan = x + j;
                        yscan = y - j;
                        break;

                    case 5:
                        xscan = x + j;
                        yscan = y + j;
                        break;

                    case 6:
                        xscan = x - j;
                        yscan = y + j;
                        break;

                    case 7:
                        xscan = x - j;
                        yscan = y - j;
                        break;
                    }

                    if (xscan >= 0 && xscan < width && yscan >= 0 && yscan < height)
                    {
                        if (!bitmap[xscan + yscan * width])
                        {
                            result[i]            = Identify(bitmap, width, height, xscan + yscan * width, digitBitmaps, instantMatch, maxBitmapsPerDigit, ref digit);
                            identifiedBitmaps[i] = digit;
                            breakout             = true;
                            break;
                        }
                    }
                }
            }
        }

        FailCounter = 0;
        return(ArrayHandling.Raise(result, 9, 9));
    }
示例#7
0
    /// <summary>
    /// The main working method. Handles the flow from creating a bitmap
    /// from the scanned image to analysing its digits and solving the sudoku.
    /// </summary>
    /// <param name="pixels">The scanned image as a one-dimensional array of Colors.</param>
    public void Worker(Color[] pixels)
    {
        try
        {
            var timer = System.Diagnostics.Stopwatch.StartNew();
            //cut out the sudoku part
            pixels = ArrayHandling.Cut(pixels, textureWidth, textureHeight,
                                       (textureWidth - side) / 2, (textureHeight - side) / 2, side, side);

            Status = "Creating Bitmap";                                                 //the current status is displayed while the background thread is active
            bool[] bits = Bitify(pixels, side, side, HorizontalChunks, VerticalChunks); //generate a "black-and-white" bitmap from the picture

            int[] coords;
            int   searchRadius;
            Status = "Finding Corners";

            int[][] corners = OCR.FindCorners2(bits, side, side, CornerQueueLength); //find the corners of the sudoku
            if (corners == null)
            {
                Popup.queued = new KeyValuePair <string, System.Action>(OCR.NoSudokuMessage, null);
                corners      = new int[][] { new int[] { 0, 0 }, new int[] { 0, 0 }, new int[] { 0, 0 }, new int[] { 0, 0 } };
            }

            #if WRITE_IMAGES_TO_DISK
            foreach (int[] corner in corners)
            {
                colorq.Enqueue(new KeyValuePair <int, Color>(corner[0] + corner[1] * side, Color.yellow));
            }
            #endif

            searchRadius = Mathf.RoundToInt(SearchRadiusFactor * Mathf.Sqrt(
                                                Mathf.Pow((corners[2][0] - corners[0][0]) / 9f, 2) +
                                                Mathf.Pow((corners[2][1] - corners[0][1]) / 9f, 2)
                                                ));

            Status = "Finding Digits";
            coords = OCR.FindDigitCoordinates(corners, side);             //calculate where the digits should be

            Status         = "Identifying Digits";
            unsolvedSudoku = OCR.GetSudoku(bits, side, side, coords, searchRadius, storedBitmaps, InstantMatchPercent, MaxStoredBitmapsPerDigit);             //identify all digits

            allZero = ArrayHandling.OnlyZeroes(unsolvedSudoku);

            var sb = new System.Text.StringBuilder();
            for (int y = 8; y >= 0; y--)
            {
                for (int x = 0; x < 9; x++)
                {
                    sb.Append(unsolvedSudoku[x, y]);
                }
                sb.AppendLine();
            }
            print(sb);

            Status       = "Solving Sudoku";
            solvedSudoku = ArrayHandling.Solve(unsolvedSudoku, ref success);             //solve the sudoku

            if (!success)
            {
                Popup.queued = new KeyValuePair <string, System.Action>(CannotSolveMessage, null);
            }

            sb = new System.Text.StringBuilder();
            for (int y = 8; y >= 0; y--)
            {
                for (int x = 0; x < 9; x++)
                {
                    sb.Append(solvedSudoku[x, y]);
                }
                sb.AppendLine();
            }
            print(sb);

            #if WRITE_IMAGES_TO_DISK
            Status = "Rendering Debug Picture";
            for (int i = 0; i < pixels.Length; i++)
            {
                pixels[i] = bits[i] ? Color.white : Color.black;
            }

            while (colorq.Count > 0)
            {
                var kvp = colorq.Dequeue();
                if (kvp.Key >= 0 && kvp.Key < pixels.Length)
                {
                    pixels[kvp.Key] = kvp.Value;
                }
            }
            #endif
            debugPicture = pixels;


            timer.Stop();
            print("Done");
            print("Time: " + timer.ElapsedMilliseconds + " ms");
            Status = StatusDone;             //signal to the main thread that the solved sudoku is availible
        }
        catch (System.Exception e)
        {
            print(e);
            Status = StatusError + e.Message;
        }
    }
示例#8
0
    /// <summary>
    /// Main program loop.
    /// </summary>
    void Update()
    {
        if (!taken)
        {
            if ((TakePictureNow && texture != null && texture.isPlaying) || Input.GetKey(KeyCode.Return) || Input.GetKey(KeyCode.Space))
            {
                //grab the image data from the WebCamTexture
                taken    = true;
                original = texture.GetPixels();
                Color[] pixels = new Color[original.Length];
                System.Array.Copy(original, pixels, original.Length);
                texture.Stop();
                print(string.Format("Took {0}x{1} picture", texture.width, texture.height));

                //refresh the settings and rotate the texture if it is needed
                Status = "Loading Settings";
                SettingManager.LoadSettings();
                ArrayHandling.RotateTexture(ref pixels, texture.width, texture.height, texture.videoRotationAngle);

                //start a new worker thread
                debugText.enabled = true;
                if (workThread != null && workThread.IsAlive)
                {
                    workThread.Abort();
                    workThread.Join(200);
                }
                workThread = new System.Threading.Thread(() => Worker(pixels));
                workThread.Start();

                PicturePanel.SetActive(false);
                CameraUI.transform.parent.gameObject.SetActive(false);
            }
        }
        //check if the worker thread has completed its tasks
        else if (Status == StatusDone)         //this works, but an enum would probably be better suited for this kind of check
        {
            Status            = "";
            debugText.enabled = false;

            FillSudoku(solvedSudoku, unsolvedSudoku);
            print("Displayed the result");

            #if WRITE_IMAGES_TO_DISK
            Texture2D originalTexture  = new Texture2D(texture.width, texture.height),
                      processedTexture = new Texture2D(side, side);
            originalTexture.filterMode = FilterMode.Point;
            originalTexture.SetPixels(original);
            originalTexture.Apply();
            processedTexture.filterMode = FilterMode.Point;
            processedTexture.SetPixels(debugPicture);
            processedTexture.Apply();

            File.WriteAllBytes(Path.Combine(BitmapEncoding.PersistentPath, "Output.png"), processedTexture.EncodeToPNG());
            File.WriteAllBytes(Path.Combine(BitmapEncoding.PersistentPath, "Input.png"), originalTexture.EncodeToPNG());
            print("Saved images to " + BitmapEncoding.PersistentPath);
            #endif
        }

        //update explanatory text if it is needed
        if (debugText.enabled && Status != oldStatus)
        {
            if (Status.Contains(StatusError))
            {
                debugText.text = Status;
            }
            else
            {
                debugText.text = (Status.Length != 0 ? "Please Wait" + System.Environment.NewLine + Status : "");
            }
            oldStatus = Status;
        }

        //trigger popup if needed
        if (Popup.queued != null)
        {
            Popup.ActivatePopup(Popup.queued.Value.Key, Popup.queued.Value.Value);
            Popup.queued = null;
        }

        //if the user presses escape (back button on Android), go back/abort calculation/exit the app
        if (Input.GetKeyDown(KeyCode.Escape))
        {
            var eb = PicturePanel.GetComponentsInChildren <ExtraButtons>(true)[0];
            if (PicturePanel.activeInHierarchy || (debugText.enabled && workThread != null && workThread.IsAlive))
            {
                Application.Quit();
            }
            else if (infoPopup.gameObject.activeInHierarchy)
            {
                infoPopup.Close();
            }
            else if (QuestionPopup.Instance.gameObject.activeInHierarchy)
            {
                QuestionPopup.Instance.Close(false);
            }
            else if (digitPrompt.gameObject.activeInHierarchy)
            {
                digitPrompt.Choose(-1);
            }
            else if (SudokuPanel.activeInHierarchy)
            {
                Reset(false);
            }
            else if (SettingManager.AdvancedSettingsPanel != null && SettingManager.AdvancedSettingsPanel.activeInHierarchy)
            {
                eb.DirectlyFromAdvanced();
            }
            else if (eb.HelpPanel != null && eb.HelpPanel.activeInHierarchy)
            {
                eb.ReturnFromHelp();
            }
        }
    }