#NoAutoIt3Execute

#include <EditConstants.au3>
#include <GUIConstantsEx.au3>
#include <Misc.au3>

Opt("GuiCloseOnEsc", 0)
Opt("GuiOnEventMode", 1)
Opt("MouseCoordMode", 2)

;~ If $CmdLine[0] > 1 And StringLen($CmdLine[1]) == 81 Then _Solve($CmdLine[1])

Global $aGuiInputs[81]
Global $aPossible[81]
Global Const $iGUISize = 500
Global Const $sEmptyChar = '0' ; nicht verändern!
Global Const $aPossibleChars[9] = [1, 2, 3, 4, 5, 6, 7, 8, 9]
Global Const $sPossibleChars = '123456789'
Global Const $sTitle = "SUDOKU-SOLVE-ENGINE by oetzn"

Global Const $bDebug = True
Global Const $bTime = True

$hGui = GUICreate($sTitle, $iGUISize, $iGUISize)

$Menu1 = GUICtrlCreateMenu("Sudoku")
GUICtrlCreateMenuItem("Einfügen", $Menu1)
GUICtrlSetOnEvent(-1, "_Insert")
GUICtrlCreateMenuItem("Lösen", $Menu1)
GUICtrlSetOnEvent(-1, "_Solve")
GUICtrlCreateMenuItem("Datei laden", $Menu1)
GUICtrlSetOnEvent(-1, "_LoadFile")
GUICtrlSetState(-1, $GUI_DISABLE)
GUICtrlCreateMenuItem("", $Menu1)
GUICtrlCreateMenuItem("Beenden", $Menu1)
GUICtrlSetOnEvent(-1, "_Exit")
$Menu2 = GUICtrlCreateMenu("Hilfe")
GUICtrlCreateMenuItem("Über", $Menu2)

_CreateInputs($iGUISize / 13)

GUISetState(@SW_SHOW, $hGui)
GUISetOnEvent(-3, "_Exit", $hGui)

HotKeySet("^v", "_SudokuFromClipboard")

While 1
	Sleep(10)
WEnd

Func _CreateInputs($iSize)
	Local $x, $y
	Local $iXExtra, $iYExtra
	Local $iCounter = 0

	For $x = 1 To 9
		For $y = 1 To 9
			$aGuiInputs[$iCounter] = GUICtrlCreateInput("", $y * $iSize + $iYExtra, $x * $iSize + $iXExtra, $iSize, $iSize, BitAND($ES_CENTER, $ES_NUMBER))
			$iCounter += 1
			GUICtrlSetFont(-1, $iSize / 2)
			If Mod($y, 3) = 0 Then $iYExtra += $iSize
		Next
		$iYExtra = 0
		If Mod($x, 3) = 0 Then $iXExtra += $iSize
	Next
EndFunc   ;==>_CreateInputs

Func _GetContent()
	If Not IsArray($aGuiInputs) Then Return
	Local $sSudoku

	For $x = 0 To 80
		If GUICtrlRead($aGuiInputs[$x]) = '' Then
			$sSudoku &= $sEmptyChar
		Else
			$sSudoku &= GUICtrlRead($aGuiInputs[$x])
		EndIf
	Next
	Return $sSudoku
EndFunc   ;==>_GetContent

Func _Insert()
	$sRet = InputBox("Sudoku einfügen!", "Bitte geben Sie hier einen String aus 81 Zahlen ein." & @LF & "Der String wird von links nach rechts eingetragen!")
	If @error Then Return
	If Not _ValidateSudoku($sRet) Then
		MsgBox(16, "ERROR", "Ein Fehler ist aufgetreten!" & @CRLF & "Das Sudoku ist nicht gültig!", 5)
		Return
	EndIf

	For $x = 0 To 80
		$sTempChar = StringLeft($sRet, 1)
		$sRet = StringTrimLeft($sRet, 1)
		If $sTempChar = $sEmptyChar Then
			$aPossible[$x] = $sPossibleChars
			ContinueLoop
		EndIf
		GUICtrlSetData($aGuiInputs[$x], '')
		GUICtrlSetData($aGuiInputs[$x], $sTempChar)
		$aPossible[$x] = $sTempChar
	Next

	If $bDebug Then AdlibRegister("_Debug", 10)

EndFunc   ;==>_Insert

Func _Solve()
	Local $sSudoku = _GetContent()

	If Not _ValidateSudoku($sSudoku) Then
		MsgBox(16, "ERROR", "Ein Fehler ist aufgetreten!" & @CRLF & "Das Sudoku ist nicht gültig!")
		Return
	EndIf

	Do

		$iEmptyFields = __Solve_Method_CheckChange()

		__Solve_Method_EliminatePossibilitesInRow($sSudoku)
		__Solve_Method_EliminatePossibilitesInColumn($sSudoku)
		__Solve_Method_EliminatePossibilitesInSquare($sSudoku)

		__Solve_Method_OnlyOneOccurenceInRow()
		__Solve_Method_OnlyOneOccurenceInColumn()
		__Solve_Method_OnlyOneOccurenceInSquare()

		__Solve_Method_CheckForSinglePoss()

	Until $iEmptyFields == __Solve_Method_CheckChange()

EndFunc   ;==>_Solve





Func __Solve_Method_EliminatePossibilitesInRow($sInput)
	; by oetzn (autoit.de)

	If Not _ValidateSudoku($sInput) Then Return
	Local $iCount
	Local $iPos
	Local $sTempRow

	If $bTime Then $TempTimer = TimerInit()

	For $i = 1 To 81 Step 9
		$sTempRow = StringMid($sInput, $i, 9)
		For $j = 1 To 9
			$iCount = 0
			$iPos = ''
			For $k = 1 To 9
				If StringMid($sTempRow, $k, 1) == $j Then
					$iCount += 1
					$iPos = $k
				EndIf
			Next
			If $iCount == 1 Then
				For $m = 1 To 9
					If $m == $iPos Then ContinueLoop
					$aPossible[$i + $m - 2] = StringReplace($aPossible[$i + $m - 2], String($j), '')
				Next
			EndIf
		Next
	Next

	If $bTime Then ConsoleWrite("__Solve_Method_EliminatePossibilitesInRow: " & TimerDiff($TempTimer) & " ms " & @CRLF)

EndFunc   ;==>__Solve_Method_EliminatePossibilitesInRow

Func __Solve_Method_EliminatePossibilitesInColumn($sInput)
	; by oetzn (autoit.de)

	If Not _ValidateSudoku($sInput) Then Return

	Local $iPosInStr = 1
	Local $sTempColumn

	If $bTime Then $TempTimer = TimerInit()

	For $iOffset = 0 To 8
		$sTempColumn = ''

		For $i = 1 To 81 Step 9
			$sTempColumn &= StringMid($sInput, $i + $iOffset, 1)
		Next

		For $j = 1 To 9
			$iCount = 0
			$iPos = ''
			For $k = 1 To 9
				If StringMid($sTempColumn, $k, 1) == $j Then
					$iCount += 1
					$iPos = $k
				EndIf
			Next
			If $iCount == 1 Then
				For $m = 1 To 9
					If $m == $iPos Then ContinueLoop
					$aPossible[$iPosInStr + ($m - 1) * 9 - 1] = StringReplace($aPossible[$iPosInStr + ($m - 1) * 9 - 1], String($j), '')
				Next
			EndIf
		Next
		$iPosInStr += 1
	Next

	If $bTime Then ConsoleWrite("__Solve_Method_EliminatePossibilitesInColumn: " & TimerDiff($TempTimer) & " ms " & @CRLF)

EndFunc   ;==>__Solve_Method_EliminatePossibilitesInColumn

Func __Solve_Method_EliminatePossibilitesInSquare($sInput)
	; by oetzn (autoit.de)

	If Not _ValidateSudoku($sInput) Then Return

	Local $aParts[27]
	Local $iCounter = 0
	Local $sTempSquare
	Local $iOffsetExt

	If $bTime Then $TempTimer = TimerInit()

	For $i = 1 To 81 Step 3
		$aParts[$iCounter] = StringMid($sInput, $i, 3)
		$iCounter += 1
	Next

	For $iOffsetBig = 0 To 26 Step 9
		For $iOffsetSmall = 0 To 2
			$sTempSquare = ''
			For $i = 0 To 8 Step 3
				$sTempSquare &= $aParts[$i + $iOffsetSmall + $iOffsetBig]
			Next

			For $j = 1 To 9
				$iCount = 0
				$iPos = ''
				For $k = 1 To 9
					If StringMid($sTempSquare, $k, 1) == $j Then
						$iCount += 1
						$iPos = $k
					EndIf
				Next
				If $iCount == 1 Then
					For $m = 1 To 9
						If $m == $iPos Then ContinueLoop
						$iOffsetExt = 0
						Select
							Case $m <= 3
								$iOffsetExt = 0
							Case $m > 3 And $m <= 6
								$iOffsetExt = 5 + $m
							Case $m >= 7
								$iOffsetExt = 11 + $m
						EndSelect
						$aPossible[$iOffsetBig * 3 + $iOffsetSmall * 3 + $iOffsetExt] = StringReplace($aPossible[$iOffsetBig * 3 + $iOffsetSmall * 3 + $iOffsetExt], String($j), '')
					Next
				EndIf
			Next

		Next
	Next

	If $bTime Then ConsoleWrite("__Solve_Method_EliminatePossibilitesInSquare: " & TimerDiff($TempTimer) & " ms " & @CRLF)

EndFunc   ;==>__Solve_Method_EliminatePossibilitesInSquare

Func __Solve_Method_OnlyOneOccurenceInRow()
	; by oetzn (autoit.de)

	Local $iCount
	Local $iPos

	If $bTime Then $TempTimer = TimerInit()

	For $i = 0 To 80 Step 9
		For $j = 1 To 9
			$iCount = 0
			$iPos = ''
			For $k = $i To $i + 8
				If StringInStr($aPossible[$k], String($j)) Then
					$iCount += 1
					$iPos = $k
				EndIf
			Next
			If $iCount == 1 Then $aPossible[$iPos] = String($j)
		Next
	Next

	If $bTime Then ConsoleWrite("__Solve_Method_OnlyOneOccurenceInRow: " & TimerDiff($TempTimer) & " ms " & @CRLF)

EndFunc   ;==>__Solve_Method_OnlyOneOccurenceInRow

Func __Solve_Method_OnlyOneOccurenceInColumn()
	; by oetzn (autoit.de)

	Local $iCount
	Local $iPos

	If $bTime Then $TempTimer = TimerInit()

	For $iOffset = 0 To 8
		For $j = 1 To 9
			$iCount = 0
			$iPos = ''
			For $i = 0 To 80 Step 9
				If StringInStr($aPossible[$i + $iOffset], String($j)) Then
					$iCount += 1
					$iPos = $i + $iOffset
				EndIf
			Next
			If $iCount == 1 Then $aPossible[$iPos] = String($j)
		Next
	Next

	If $bTime Then ConsoleWrite("__Solve_Method_OnlyOneOccurenceInColumn: " & TimerDiff($TempTimer) & " ms " & @CRLF)

EndFunc   ;==>__Solve_Method_OnlyOneOccurenceInColumn

Func __Solve_Method_OnlyOneOccurenceInSquare()
	; by oetzn (autoit.de)

	Local $iCount
	Local $iPos

	If $bTime Then $TempTimer = TimerInit()

	For $iOffset1 = 0 To 54 Step 27
		For $iOffset2 = 0 To 6 Step 3
			For $j = 1 To 9
				$iCount = 0
				$iPos = ''
				For $iOffset3 = 0 To 18 Step 9
					For $iOffset4 = 0 To 2
						If StringInStr($aPossible[$iOffset1 + $iOffset2 + $iOffset3 + $iOffset4], $j) Then
							$iCount += 1
							$iPos = $iOffset1 + $iOffset2 + $iOffset3 + $iOffset4
						EndIf
					Next
				Next
				If $iCount == 1 Then $aPossible[$iPos] = String($j)
			Next
		Next
	Next

	If $bTime Then ConsoleWrite("__Solve_Method_OnlyOneOccurenceInColumn: " & TimerDiff($TempTimer) & " ms " & @CRLF)

EndFunc   ;==>__Solve_Method_OnlyOneOccurenceInSquare





Func __Solve_Method_CheckForSinglePoss()

	If $bTime Then $TempTimer = TimerInit()

	For $i = 0 To 80
		If StringLen($aPossible[$i]) == 1 Then GUICtrlSetData($aGuiInputs[$i], $aPossible[$i])
	Next

	If $bTime Then ConsoleWrite("__Solve_Method_CheckForSinglePoss: " & TimerDiff($TempTimer) & " ms " & @CRLF)

EndFunc   ;==>__Solve_Method_CheckForSinglePoss

Func __Solve_Method_CheckChange()
	Local $iEmptyCounter = 0
	For $i = 0 To 80
		If StringLen($aPossible[$i]) > 1 Then $iEmptyCounter += 1
	Next

	Return $iEmptyCounter
EndFunc   ;==>__Solve_Method_CheckChange





Func _ValidateSudoku($sInput)
	; by oetzn (autoit.de)
	Local $bValid = True

	If StringRegExp($sInput, '[^0-9]', 0) = 1 Then $bValid = False
	If StringLen($sInput) <> 81 Then $bValid = False
	If Not IsString($sInput) Then $bValid = False

	Return $bValid
EndFunc   ;==>_ValidateSudoku

Func _Debug()
	$aInfo = GUIGetCursorInfo($hGui)
	If $aInfo[4] > 0 Then
		For $i = 0 To UBound($aGuiInputs, 1) - 1
			If $aGuiInputs[$i] == $aInfo[4] Then ExitLoop
		Next
		ToolTip("Possbile Chars: " & $aPossible[$i])
	Else
		ToolTip("")
	EndIf
EndFunc   ;==>_Debug

Func _SudokuFromClipboard()
	Local $sClipboard = ClipGet()
	If WinGetTitle("[ACTIVE]", "") == $sTitle Then
		If _ValidateSudoku($sClipboard) Then
			For $x = 0 To 80
				$sTempChar = StringLeft($sClipboard, 1)
				$sClipboard = StringTrimLeft($sClipboard, 1)
				If $sTempChar = $sEmptyChar Then
					$aPossible[$x] = $sPossibleChars
					ContinueLoop
				EndIf
				GUICtrlSetData($aGuiInputs[$x], '')
				GUICtrlSetData($aGuiInputs[$x], $sTempChar)
				$aPossible[$x] = $sTempChar
			Next
		Else
			MsgBox(16, "ERROR", "Ein Fehler ist aufgetreten!" & @CRLF & "Das Sudoku ist nicht gültig!", 2)
			Return
		EndIf

		If $bDebug Then AdlibRegister("_Debug", 10)

	Else
		HotKeySet("^v")
		Send("^v")
		HotKeySet("^v", "_SudokuFromClipboard")
	EndIf
EndFunc   ;==>_SudokuFromClipboard

Func _LoadFile()
	Sleep(10)
EndFunc   ;==>_LoadFile

Func _Exit()
	Exit
EndFunc   ;==>_Exit