#Requires AutoHotkey v2.0 ; CONTENTS: ; ; Auto-execute section: ; Basic variables and arrays ; Main GUI = Sudoku board and menu ; ; Menu items and hotkeys ; Other hotkeys ; Mouse buttons ; Subroutines A-Z SendMode "input" ; for click SetTitleMatchMode 1 ;======================================================================================== ; Basic variables and arrays ;======================================================================================== AllCells := "" loop 9 ; rows { r := a_index loop 9 ; columns { c := a_index AllCells .= "-" r c } } AllCells := LTrim(AllCells, "-") letter := ["A", "B", "C", "D", "E", "F", "G", "H", "I"] unit := [] unit.length := 27 loop 27 { if (a_index <= 9) unit[a_index] := "row " a_index else if (a_index <= 18) unit[a_index] := "column " letter[a_index-9] else unit[a_index] := "block " (a_index-18) } UnitCells := [] UnitCells.length := 27 loop 9 { i := a_index UnitCells[i] := "" loop 9 UnitCells[i] .= i a_index "-" UnitCells[i] := RTrim(UnitCells[i], "-") ; rows UnitCells[i+9] := "" loop 9 UnitCells[i+9] .= a_index i "-" UnitCells[i+9] := RTrim(UnitCells[i+9], "-") ; columns } UnitRows := [] ; blocks UnitRows.length := 27 UnitColumns := [] UnitColumns.length := 27 intersection(19, 1, 2, 3, 1, 2, 3) intersection(20, 1, 2, 3, 4, 5, 6) intersection(21, 1, 2, 3, 7, 8, 9) intersection(22, 4, 5, 6, 1, 2, 3) intersection(23, 4, 5, 6, 4, 5, 6) intersection(24, 4, 5, 6, 7, 8, 9) intersection(25, 7, 8, 9, 1, 2, 3) intersection(26, 7, 8, 9, 4, 5, 6) intersection(27, 7, 8, 9, 7, 8, 9) num := map() PencilMark := map() block := map() coord := map() ConnectedCells := map() loop parse, AllCells, "-" { rc := a_loopfield r := SubStr(rc, 1, 1) c := SubStr(rc, 2, 1) num[rc] := 0 ; 0 if the cell is empty, else the number in the cell loop 9 PencilMark[rc a_index] := 0 ; 0 if the pencil mark is not set, 1 if it is set for u, cells in UnitCells if (u >= 19 and InStr(cells, rc)) { block[rc] := u break } coord[rc] := letter[c] r ConnectedCells[rc] := "" loop parse, UnitCells[r] "-" UnitCells[c+9] "-" UnitCells[block[rc]], "-" if (a_loopfield != rc and not InStr(ConnectedCells[rc], a_loopfield)) ConnectedCells[rc] .= "-" a_loopfield ConnectedCells[rc] := LTrim(ConnectedCells[rc], "-") } FixedCells := "" ;---- colors ---- colors := [] colors.length := 9 colors[1] := ["white", "FFFFFF"] colors[2] := ["yellow", "FFF000"] colors[3] := ["orange", "FF9900"] colors[4] := ["red", "FF0000"] colors[5] := ["purple", "800080"] colors[6] := ["blue", "0040FF"] colors[7] := ["light blue", "00FFFF"] colors[8] := ["green", "00A000"] colors[9] := ["black", "202020"] ; Without quote marks, ABCDEF would be stored as decimal, that is, as 10*16**5+11*16**4+12*16**3+13*16**2+14*16+15=11259375, ; and when used in gui.backcolor, gui.setfont, guicontrol.setfont, only the last six digits would be used, that is, the color would be 259375. ColorName := [] ColorName.length := 9 ColorNumber := map() loop 9 { ColorName[a_index] := colors[a_index][1] ; ColorName[1] = white, ColorName[2] = yellow etc. ColorNumber[colors[a_index][1]] := a_index ; ColorNumber["white"] = 1, ColorNumber["yellow"] = 2 etc. } ;---- pale colors to highlight cells ---- pale := map("red", "FFCCCC", "green", "CCFFCC", "blue", "CCCCFF", "orange", "FFCCA0") ;---- starting values ---- cNumber := map() cPencilMark := map() highlighting := map() loop parse, AllCells, "-" { cNumber[a_loopfield] := "default" highlighting[a_loopfield] := "" loop 9 cPencilMark[a_loopfield a_index] := "default" } display := "numbers" cBackgroundForNumbers := "FFFFFF" cBackgroundForColors := "FFEECC" cDefault := "202020" cFixed := "007000" ;======================================================================================== ; Main GUI = Sudoku board and menu ;======================================================================================== ; The Sudoku board is actually a bunch of text controls: ; - GreySquare11 to 81 completely cover the Sudoku board. ; - WhiteSquare11 to 81 are on top of the grey squares and somewhat smaller than the grey squares, so they appear as the ; white squares of the Sudoku board, and the grey squares behind them appear to be the grid. ; - number11 to 81 contain the numbers of the Sudoku: num[11] to num[81] are the numbers set in number11 to number81. ; number11 to 81 are transparent and exactly on top of WhiteSquare11 to 81, so that numbers appear to be set in the white ; squares. ; - PencilMark111 to 819 contain the pencil marks of the Sudoku: PencilMark[111] to PencilMark[819] are the pencil marks set ; in PencilMark111 to 819. PencilMark111 to 819 are transparent and there are 9 pencil mark text controls on top of each ; white square. ; g in Webdings: ; A g in Webdings is a square, and when it completely covers its text control, it "colors" the text control. ; - Dark grey squares in the grey squares are used to make the grid more distinct. (Without Webdings, the option -background ; for the grey squares uses the standard background color rather than the one set by the gui color command, so there is still ; a grid but paler.) ; - Red or green or blue squares in the white squares are used to highlight the squares. (=> Without Webdings, there will be ; little red and green and blue g-s in the "highlighted" squares.) highlighting[11] to highlighting[81] are the highlighting colors ; in WhiteSquare11 to 81. Numbers and pencil marks in highlighted squares must be on top of the highlighting squares in order ; to not be covered, too! MainGui := gui() loop parse, AllCells, "-" { MainGui.add("text", "vGreySquare" a_loopfield " -background", "g") MainGui.add("text", "vWhiteSquare" a_loopfield) MainGui.add("text", "vnumber" a_loopfield " +center backgroundtrans") loop 9 MainGui.add("text", "vPencilMark" a_loopfield a_index " +center backgroundtrans") } caption123 := "1-2-3-4-5-6-7-8-9" loop parse, caption123, "-" MainGui.add("text", "vcaption123" a_loopfield " +center", a_loopfield) captionABC := "A-B-C-D-E-F-G-H-I" loop parse, captionABC, "-" MainGui.add("text", "vcaptionABC" a_loopfield " +center", a_loopfield) ; Arrays for the parameters of the text controls' text: cTextControl := map() ; color sTextControl := map() ; size wTextControl := map() ; weight fTextControl := map() ; font loop parse, AllCells, "-" { cTextControl["WhiteSquare" a_loopfield] := "" sTextControl["WhiteSquare" a_loopfield] := "" fTextControl["WhiteSquare" a_loopfield] := "" cTextControl["number" a_loopfield] := "" sTextControl["number" a_loopfield] := "" fTextControl["number" a_loopfield] := "" loop 9 { cTextControl["PencilMark" a_loopfield a_index] := "" sTextControl["PencilMark" a_loopfield a_index] := "" wTextControl["PencilMark" a_loopfield a_index] := "" ; Pencil marks can be bold. fTextControl["PencilMark" a_loopfield a_index] := "" } } ; cTextControl["number" cell] is cNumber[cell] if display="numbers", or ColorName[n] if display="colors". ; If cNumber[cell] is "default", its value is cDefault, or cFixed if the number is fixed. ; cTextControl["PencilMark" cell n] is cPencilMark[cell n] if display="numbers", or ColorName[n] if display="colors". ; If cPencilMark[cell n] is "default", its value is cDefault. ; The values of cDefault and cFixed are set and changed by background(). ; cTextControl is always black in highlighted cells. SudokuMenu := Menu() SudokuMenu.Add "&Easy and symmetrical", EasySudoku SudokuMenu.Add "&Difficult but not symmetrical", DifficultSudoku SudokuMenu.Add "&Open...", open SudokuMenu.Add "&Fix", fix SudokuMenu.Add "&Save as...", SaveAs SudokuMenu.Add SudokuMenu.Add "home", home SudokuMenu.Add "tree", tree SudokuMenu.Add "autumn tree", AutumnTree SudokuMenu.Add "spiral", spiral SudokuMenu.Add "heart", heart SudokuMenu.Add "smiley", smiley SudokuMenu.Add "Christmas tree", ChristmasTree SudokuMenu.Add "crown", crown SudokuMenu.Add "sun", sun SudokuMenu.Add "star", star SudokuMenu.Add SudokuMenu.Add "Create image...", PaintImage ViewMenu := Menu() ViewMenu.Add "Larger Ctrl++", larger ViewMenu.Add "Smaller Ctrl+-", smaller ViewMenu.Add "Normal size Ctrl+N", NormalSize ViewMenu.Add "Mix a color... Ctrl+C", MixColor ViewMenu.Add "S&witch from numbers to colors", MySwitch PlayMenu := Menu() PlayMenu.Add "Back page up", back PlayMenu.Add "Forward page down", forward PlayMenu.Add "&All pencil marks", AllPencilMarks PlayMenu.Add "&Pencil mark singles or pairs", PencilMarkSinglesOrPairs PlayMenu.Add "&No pencil marks", NoPencilMarks PlayMenu.Add "&Clear board", ClearBoard SolveMenu := Menu() SolveMenu.Add "Set and explain one Ctrl+page down", SetOne SolveMenu.Add "Set &all", SetAll Menus := MenuBar() Menus.Add "&Sudoku", SudokuMenu ; Attach the two submenus that were created above. Menus.Add "&View", ViewMenu Menus.Add "&Play", PlayMenu Menus.Add "S&olve", SolveMenu Menus.Add "&?", help MainGui.MenuBar := Menus zoom := 1 SizeAndPosition() background("FFFFFF") MainGui.title := "Sudoku" MainGui.show("w" wGui " h" wGui) history := ["numbers/FFFFFF"] HistoryIndex := 1 context := "" AutoPencil := 0 today := A_MM A_DD if (today >= 1201 and today <= 1224) star else if (today >= 1225 or today <= 0106) ChristmasTree MainGui.OnEvent("close", MainGuiClose) MainGuiClose(GuiObj) { global MainGui.Opt("+OwnDialogs") something := 0 loop parse, AllCells, "-" { if (num[a_loopfield] != 0) something += 1 else loop 9 if (PencilMark[a_loopfield a_index] = 1) something += 1 } if something > 3 { answer := msgbox("Do you want to save the current situation?",, "YesNoCancel") if (answer = "Yes") SaveAs else if (answer = "Cancel") return 1 } exitapp } return ;======================================================================================== ; Menu items and hotkeys ;======================================================================================== #HotIf WinActive("Sudoku ahk_class AutoHotkeyGUI") EasySudoku(*) { global MainGui.Opt("+Disabled") context .= "-EasySudoku" ; for lbutton, rbutton PleaseWait ClearBoard fill("MyRandom") RandomCells := sort(AllCells, "random d-") SymCells := "" loop parse, RandomCells, "-" if not InStr(SymCells, a_loopfield) { sym1 := a_loopfield r := SubStr(sym1, 1, 1) c := SubStr(sym1, 2, 1) sym2 := r 10-c sym3 := 10-r c sym4 := 10-r 10-c sym5 := c r sym6 := c 10-r sym7 := 10-c r sym8 := 10-c 10-r SymCells .= "-" sym1 "-" sym2 "-" sym3 "-" sym4 "-" sym5 "-" sym6 "-" sym7 "-" sym8 loop 8 num[sym%a_index%] := 0 fill("minimum", 0) loop parse, FillCells, "-" num[a_loopfield] := 0 if (bifurcation.length = 0) { loop 8 SetNumber(sym%a_index%, 0) } else { fill("maximum", 0) differences := 0 loop parse, FillCells, "-" { num[a_loopfield] := 0 if (minimum[a_loopfield] != maximum[a_loopfield]) differences += 1 } if (differences = 0) { loop 8 SetNumber(sym%a_index%, 0) } else { loop 8 num[sym%a_index%] := MyRandom[sym%a_index%] } } } if (display = "numbers") loop parse, AllCells, "-" if (num[a_loopfield] != 0) { r := SubStr(a_loopfield, 1, 1) c := SubStr(a_loopfield, 2, 1) GreenAdd := abs(r-5)*48 BlueAdd := abs(c-5)*48 RedShade := Format("{:X}", 255*16**4+GreenAdd*16**2+BlueAdd) cNumber[a_loopfield] := RedShade font("number" a_loopfield, RedShade, sNumber, "Arial") } fix HistoryPush MainGui.Opt("-Disabled") context := StrReplace(context, "-EasySudoku") GuiPleaseWait.destroy() } ;---------------------------------------------------------------------------------------- DifficultSudoku(*) { global MainGui.Opt("+Disabled") context .= "-DifficultSudoku" ; for lbutton, rbutton PleaseWait CancelDifficultSudoku := 0 ClearBoard fill("MyRandom") RandomCells := sort(AllCells, "random d-") RandomCells := SubStr(RandomCells, 1, 119) loop parse, RandomCells, "-" { if a_index < 4 { SetNumber(a_loopfield, 0) continue } num[a_loopfield] := 0 fill("minimum", 0) loop parse, FillCells, "-" num[a_loopfield] := 0 if (bifurcation.length = 0) SetNumber(a_loopfield, 0) else { fill("maximum", 0) differences := 0 loop parse, FillCells, "-" { num[a_loopfield] := 0 if (minimum[a_loopfield] != maximum[a_loopfield]) differences += 1 } if (differences = 0) SetNumber(a_loopfield, 0) else num[a_loopfield] := MyRandom[a_loopfield] } } loop { if CancelDifficultSudoku { MainGui.Opt("-Disabled") context := StrReplace(context, "-DifficultSudoku") GuiPleaseWait.destroy() return } PossibleOmit := map() loop parse, AllCells, "-" if (num[a_loopfield] != 0) { num1 := num[a_loopfield] num[a_loopfield] := 0 fill("minimum", 0) loop parse, FillCells, "-" num[a_loopfield] := 0 if (BifurcationCount = 0) { PossibleOmit[a_loopfield] := 0 num[a_loopfield] := num1 } else if (bifurcation.length = 0) { BifurcationCountAdd := BifurcationCount fill("maximum", 0) BifurcationCountAdd += BifurcationCount loop parse, FillCells, "-" num[a_loopfield] := 0 fill("MyRandom", 0) BifurcationCountAdd += BifurcationCount loop parse, FillCells, "-" num[a_loopfield] := 0 PossibleOmit[a_loopfield] := BifurcationCountAdd num[a_loopfield] := num1 } else { BifurcationCountAdd := BifurcationCount fill("maximum", 0) BifurcationCountAdd += BifurcationCount differences := 0 loop parse, FillCells, "-" { num[a_loopfield] := 0 if (minimum[a_loopfield] != maximum[a_loopfield]) differences += 1 } if (differences = 0) { fill("MyRandom", 0) BifurcationCountAdd += BifurcationCount loop parse, FillCells, "-" num[a_loopfield] := 0 PossibleOmit[a_loopfield] := BifurcationCountAdd } num[a_loopfield] := num1 } } if (PossibleOmit.count = 0) break for rc, BifurcationCount in PossibleOmit { if (a_index = 1 or BifurcationCount > BifurcationCountMax) { BifurcationCountMax := BifurcationCount BestOmit := rc "-" } else if (BifurcationCount = BifurcationCountMax) BestOmit .= rc "-" } BestOmit := sort(BestOmit, "random d-") BestOmit := SubStr(BestOmit, 1, 2) SetNumber(BestOmit, 0) } fix HistoryPush MainGui.Opt("-Disabled") context := StrReplace(context, "-DifficultSudoku") GuiPleaseWait.destroy() } ;---------------------------------------------------------------------------------------- open(*) { global MainGui.Opt("+OwnDialogs") if not FileExist(a_desktop "\Sudoku") DirCreate a_desktop "\Sudoku" Sudoku := FileSelect(, a_desktop "\Sudoku") if (Sudoku != "") { MyString := FileRead(Sudoku) StringToGui(MyString) HistoryPush AutoPencil := 0 } } ;---------------------------------------------------------------------------------------- fix(*) { global FixedCells := "" loop parse, AllCells, "-" if (num[a_loopfield] != 0) FixedCells .= "-" a_loopfield FixedCells := LTrim(FixedCells, "-") if (FixedCells != "") { loop parse, FixedCells, "-" if (cNumber[a_loopfield] = "default") SetNumber(a_loopfield, num[a_loopfield]) SudokuMenu.Rename "4&", "&Unfix" SudokuMenu.Add "&Unfix", unfix HistoryPush } } unfix(*) { global if (FixedCells != "") { FixedCells2 := FixedCells FixedCells := "" ; FixedCells influences SetNumber() loop parse, FixedCells2, "-" if (cNumber[a_loopfield] = "default") SetNumber(a_loopfield, num[a_loopfield]) SudokuMenu.Rename "4&", "&Fix" SudokuMenu.Add "&Fix", fix HistoryPush } } ;---------------------------------------------------------------------------------------- SaveAs(*) { global MainGui.Opt("+OwnDialogs") if not FileExist(a_desktop "\Sudoku") DirCreate a_desktop "\Sudoku" Sudoku := FileSelect("S16", a_desktop "\Sudoku") if (Sudoku != "") { MyString := GuiToString() if not InStr(SubStr(Sudoku, -4), ".") ; if the user didn't write an extension Sudoku .= ".txt" if FileExist(Sudoku) FileDelete Sudoku FileAppend MyString, Sudoku } } ;---------------------------------------------------------------------------------------- home(*) { global MyString := "numbers/FFFFFF/152,244,265,334,376,424,485,529,541,558,567,582,621,646,664,688,727,749,768,781,828,832,847,861,874,886/" StringToGui(MyString) AutoPencil := 0 HistoryPush } ;---------------------------------------------------------------------------------------- tree(*) { global MyString := "numbers/FFFFFF/137green,149green,151green,164green,223green,271green,316green," . "388green,418green,485green,512green,589green,625green,674green,731green,743804000," . "754804000,767green,842804000,855804000,941804000,956804000" StringToGui(MyString) fix AutoPencil := 0 HistoryPush } ;---------------------------------------------------------------------------------------- AutumnTree(*) { global MyString := "numbers/FFFFFF/133FF6400,144FFB700,1597D7327,165FFB700,226FF4000,275FF6400," . "317FF6400,381FF4000,412FFB700,483FF6400,5117D7327,588FFB700,628FFB700,6797D7327,735FF6400," . "743654000,756654000,769FFB700,842654000,855654000,941654000,954654000" StringToGui(MyString) fix AutoPencil := 0 HistoryPush } ;---------------------------------------------------------------------------------------- spiral(*) { global MyString := "numbers/FFFFFF/24240FF,25740FF,26540FF,332FF0000,37940FF,427FF0000," . "456FFF000,48440FF,525FF0000,543FFF000,561FFF000,586A000,621FF0000,662FFF000," . "688A000,733FF0000,748FF0000,759FFF000,782A000,875A000,967A000" StringToGui(MyString) fix AutoPencil := 0 HistoryPush } ;---------------------------------------------------------------------------------------- heart(*) { global MyString := "numbers/FFFFFF/221CC0000,239CC0000,273CC0000,286CC0000,314CC0000," . "348CC0000,362CC0000,391CC0000,412CC0000,454CC0000,498CC0000,511CC0000," . "599CC0000,627CC0000,683CC0000,733CC0000,775CC0000,849CC0000,861CC0000,957CC0000" StringToGui(MyString) fix AutoPencil := 0 HistoryPush } ;---------------------------------------------------------------------------------------- smiley(*) { global MyString := "numbers/FFFF77/147FF2C00,155FF2C00,161FF2C00,231FF2C00,278FF2C00,327FF2C00,346FF2C00," . "369FF2C00,381FF2C00,428FF2C00,484FF2C00,525FF2C00,536FF2C00,573FF2C00,587FF2C00,621FF2C00," . "642FF2C00,657FF2C00,664FF2C00,685FF2C00,733FF2C00,771FF2C00,845FF2C00,853FF2C00,862FF2C00" StringToGui(MyString) fix AutoPencil := 0 HistoryPush } ;---------------------------------------------------------------------------------------- ChristmasTree(*) { global MyString := "colors/FFEECC/114,152,196,251,343,364,448,465,532,578,637,673,721,788,828,835,842," . "857,863,871,884,959" StringToGui(MyString) fix AutoPencil := 0 HistoryPush } ;---------------------------------------------------------------------------------------- crown(*) { global MyString := "colors/FFEECC/216,254,298,312,323,345,368,384,396,413,432,476,495,514,592,618,651," . "694,717,791,811,828,834,842,859,866,873,885,897" StringToGui(MyString) fix AutoPencil := 0 HistoryPush } ;---------------------------------------------------------------------------------------- sun(*) { global MyString := "colors/DDEEFF/136,168,193,242,263,287,351,372,411,426,435,443,482,498,552,571,644," . "661,683,734,762,797,823,866,912,967" StringToGui(MyString) fix AutoPencil := 0 HistoryPush } ;---------------------------------------------------------------------------------------- star(*) { global MyString := "numbers/70/151FFFFFF,221FFFFFF,254FFFFFF,287FFFFFF,342FFFFFF,365FFFFFF,431FFFFFF," . "449FFFFFF,468FFFFFF,472FFFFFF,512FFFFFF,528FFFFFF,584FFFFFF,597FFFFFF,633FFFFFF,644FFFFFF," . "667FFFFFF,675FFFFFF,745FFFFFF,769FFFFFF,827FFFFFF,858FFFFFF,881FFFFFF,957FFFFFF/" StringToGui(MyString) fix AutoPencil := 0 HistoryPush } ;---------------------------------------------------------------------------------------- PaintImage(*) { global ; Overview: ; Paint an image ; Check the image: ; 1. Less than 17 cells? ; 2. No pencil marks? ; 3. Two numbers missing completely? ; 4. Two rows or columns in the same blocks completely empty? ; 5. Less than n possible numbers in n cells in one unit? ; Create a Sudoku from the image: ; Fill pencilled cells with random numbers. ; loop images ; { ; loop pencilled cells ; { ; Try all pencilled and possible numbers in a_loopfield. ; If the resulting image is already in AllImages, skip the number, else add the image to AllImages. ; Fill the board with minimum/maximum/random numbers, and count the differences. ; If there are no differences, there is only one solution, and the Sudoku is ready. ; Otherwise, add the image to SortedImages. ; } ; Take the best image from SortedImages and set it as image for the next loop. ; If there are no more images in SortedImages, msgbox "failed". ; } if WinExist("Paint image ahk_class AutoHotkeyGUI") { GuiPaintImage.show return } if WinExist("Create image ahk_class AutoHotkeyGUI") { GuiCreateImage.show return } if FileExist(a_desktop "\Sudoku\Create image\image.txt") and msgbox("The last creating was canceled. Do you want to continue it?",, "YesNo default2") = "Yes" { CreateImage("","", 1) return } unfix AutoPencil := 0 loop parse, AllCells, "-" highlight(a_loopfield, "") tooltip GuiPaintImage := gui() GuiPaintImage.setfont("s12") GuiPaintImage.add("text",, ( "Paint an image with pencil marks and " display ": Set/delete " display " and pencil marks as usual. The P key sets all possible pencil marks in a cell. The program will try to create a Sudoku from your image: Empty cells will be left empty, preset " display " will remain unchanged, cells with pencil marks will be filled with one of the pencil marked " display ". For a reasonable chance to create a Sudoku from the image, there should be preset " display " or pencil marks in at least 20 cells." )) button := GuiPaintImage.add("button", "xs y+20", "Create image") button.OnEvent("click", CreateImage) button := GuiPaintImage.add("button", "x+20", "Cancel") button.OnEvent("click", GuiPaintImageClose) GuiPaintImage.OnEvent("size", GuiPaintImageArrange) GuiPaintImageArrange(GuiPaintImage, MinMax, GuiPaintImageWidth, GuiPaintImageHeight) { ArrangeWindows(GuiPaintImage, GuiPaintImageWidth+10, GuiPaintImageHeight+40) ; + borders, title bar and some space } GuiPaintImage.title := "Paint image ..." GuiPaintImage.show } GuiPaintImageClose(*) { global GuiPaintImage.destroy() } CreateImage(button, info, ContinueLastCreating:=0) { global AutoPencil := 0 MainGui.Opt("+OwnDialogs") MainGui.show if ContinueLastCreating { ImageString := FileRead(a_desktop "\Sudoku\Create image\image.txt") StringToGui(ImageString) HistoryPush } else ImageString := GuiToString() ImageCells := "" PencilledCells := "" PresetCells := "" FillCells := "" loop parse, AllCells, "-" { if (num[a_loopfield] != 0) ImageCells .= a_loopfield "-" else loop 9 if (PencilMark[a_loopfield a_index] = 1) { ImageCells .= a_loopfield "-" break } } ImageCells := RTrim(ImageCells, "-") loop parse, ImageCells, "-" if (num[a_loopfield] = 0) PencilledCells .= a_loopfield "-" else PresetCells .= a_loopfield "-" PencilledCells := RTrim(PencilledCells, "-") PresetCells := RTrim(PresetCells, "-") loop parse, AllCells, "-" if not InStr(ImageCells, a_loopfield) FillCells .= a_loopfield "-" FillCells := RTrim(FillCells, "-") if (StrLen(ImageCells) < 50) ;---- Less than 17 cells? ---- { msgbox "There have to be preset " display " or pencil marks in at least 17 cells. (The fewest " display " possible for a proper Sudoku is 17.)" return } if (PencilledCells = "") ;---- No pencil marks? ---- { msgbox "There are no pencil marks.`nThere have to be pencil marks so that the Sudoku creator can try different " display "." return } missing := "123456789" ; ---- Two numbers missing completely? ---- loop parse, ImageCells, "-" { missing := StrReplace(missing, num[a_loopfield]) loop 9 if (PencilMark[a_loopfield a_index] = 1) missing := StrReplace(missing, a_index) } if (StrLen(missing) >= 2) { missing1 := SubStr(missing, 1, 1) missing2 := SubStr(missing, 2, 1) msgbox "There can be no unique solution because " item(missing1) " and " item(missing2) " are missing completely and could be swapped." return } loop 6 ;---- Two rows or columns in the same blocks completely empty? ---- { i0 := a_index*3-3 ; i1 and i2 must be in the same blocks = both in unit 1, 2, 3 or 4, 5, 6 or 7, 8, 9 etc. i0 serves as starting point to find i1 and i2. i1 := "" loop 3 { i2 := i0+a_index ; i0 = 0 => i2 = 1, 2, 3 etc. empty := 1 loop parse, UnitCells[i2], "-" if InStr(ImageCells, a_loopfield) { empty := 0 break } if (empty = 1) { if (i1 = "") i1 := i2 else { msgbox "There can be no unique solution because " unit[i1] " and " unit[i2] " are completely empty and could be swapped." return } } } } loop 27 ;---- Less than n possible numbers in n cells in one unit? ---- { u := a_index PencilCellsInU := [] loop parse, UnitCells[u], "-" if InStr(PencilledCells, a_loopfield) loop 9 if (PencilMark[a_loopfield a_index] = 0) ; at least one pencil mark not set { PencilCellsInU.push(a_loopfield) break } if (PencilCellsInU.length < 2) continue ; next unit patterns := [12, 123, 1234, 12345, 123456, 1234567, 12345678, 123456789] ; The patterns will be permutated: e.g. 1234 -> 1235, 1236, 1237, 1238, 1239, 1245, 1246, 1247 etc. ; The permutated patterns will be compared with PencilCellsInU, e.g. pattern 1247: are there less than four pencil marked numbers in cells 1, 2, 4, 7? loop 7 if (patterns.length+1 > PencilCellsInU.length) ; patterns.length+1 = length of the last (longest) pattern patterns.pop() ; Longer patterns than PencilCellsInU are not needed. for , pattern in patterns { loop ; for permutations of pattern { PencilCellsInPattern := [] PencilMarksInPattern := "" loop parse, pattern { PencilCell := PencilCellsInU[a_loopfield] ; a_loopfield, not a_index: if the pattern is 1235, a_index 4 would get cell 4 of PencilCellsInU, but a_loopfield 5 gets cell 5 of PencilCellsInU. PencilCellsInPattern.push(PencilCell) loop 9 if (PencilMark[PencilCell a_index] = 1 and not InStr(PencilMarksInPattern, a_index)) PencilMarksInPattern .= a_index } if (StrLen(PencilMarksInPattern) < StrLen(pattern)) { PencilCellsText := "" for , PencilCell in PencilCellsInPattern { PencilCellsText .= coord[PencilCell] " and " highlight(PencilCell, "blue") } PencilCellsText := SubStr(PencilCellsText, 1, -5) if (StrLen(PencilMarksInPattern) = 1) PencilMarksText := " there is only one possible " SubStr(display, 1, -1) else PencilMarksText := " there are only " StrLen(PencilMarksInPattern) " possible " display msgbox "In " PencilCellsText " " PencilMarksText ", so one cell can't be filled." ; It's always "one cell" because shorter patterns are looped first. return } pattern := NextPermutation(pattern, PencilCellsInU.length) if (pattern = "") break ; exit the loop for permutations } } } PencilledNumbers := map() loop parse, PencilledCells, "-" { PencilledNumbers[a_loopfield] := "" loop 9 if (PencilMark[a_loopfield a_index] = 1) PencilledNumbers[a_loopfield] .= a_index } AllImages := [] SortedImages := [] SortedImages.length := 200 MyCount := [] MyCount.length := 200 loop 200 { SortedImages[a_index] := [] MyCount[a_index] := 0 } BestCount := [] NotFilledContinued := 0 CancelWait := 0 ;---- Create a Sudoku from the image ---- if ContinueLastCreating { StringToGui(FileRead(a_desktop "\Sudoku\Create image\CanceledImage.txt")) AllImagesString := FileRead(a_desktop "\Sudoku\Create image\AllImages.txt") examined := 0 loop parse, AllImagesString, "-" { AllImages.push(a_loopfield) examined += 1 } loop 200 if FileExist(a_desktop "\Sudoku\Create image\SortedImages" a_index ".txt") { SortedImagesString := FileRead(a_desktop "\Sudoku\Create image\SortedImages" a_index ".txt") i := a_index loop parse, SortedImagesString, "-" SortedImages[i].push(a_loopfield) } BestCountString := FileRead(a_desktop "\Sudoku\Create image\BestCount.txt") loop parse, BestCountString, "-" loop parse, a_loopfield, "/" { if (a_index = 1) d := a_loopfield else { MyCount[d] := a_loopfield BestCount.push([d, a_loopfield]) } } } else { if not fill("MyRandom", 1, 1) { msgbox "There is no solution." HistoryPush return } loop parse, FillCells, "-" SetNumber(a_loopfield, 0) GuiPaintImage.destroy() examined := 0 } MainGui.Opt("+Disabled") GuiCreateImage := gui("+owner" MainGui.Hwnd) GuiCreateImage.setfont("s12") GuiCreateImage.add("text", "vtext1", "Loop") GuiCreateImage.add("text", "x+5 vLoopIndex w100", "") GuiCreateImage.add("text", "xm vtext2", "Examined images:") GuiCreateImage.add("text", "x+5 vexamined w100", examined) GuiCreateImage.add("text", "xm vtext3", "Differences between minimum/maximum/random solution") GuiCreateImage.add("text", "xm y+2 vtext4", "- in the last examined image:") GuiCreateImage.add("text", "x+5 vdifferences w100", "") GuiCreateImage.add("text", "xm y+2 vtext5", "- in the best images until now:") GuiCreateImage.add("text", "x+5 vBestCount w300", "") if (BestCount.length > 0) ; from ContinueLastCreating { BestCountText := "" for , dn in BestCount BestCountText .= dn[2] " x " dn[1] ", " BestCountText := RTrim(BestCountText, ", ") GuiCreateImage["BestCount"].text := BestCountText } GuiCreateImage.add("text", "xm vtext6", "`nPlease wait, this can take some time ...") CancelButton := GuiCreateImage.add("button",, "Cancel") CancelButton.OnEvent("click", CreateImageCancel) GuiCreateImage.OnEvent("close", CreateImageCancel) GuiCreateImage.OnEvent("size", GuiCreateImageArrange) GuiCreateImageArrange(GuiCreateImage, MinMax, GuiCreateImageWidth, GuiCreateImageHeight) { ArrangeWindows(GuiCreateImage, GuiCreateImageWidth+10, GuiCreateImageHeight+40) ; + borders, title bar and some space } GuiCreateImage.title := "Create image ..." GuiCreateImage.show() loop { LoopIndex := a_index GuiCreateImage["LoopIndex"].text := a_index loop parse, PencilledCells, "-" { PencilImageIndex := a_index PencilImageLoopfield := a_loopfield LoopHighlight(PencilImageLoopfield) PencilledAndPossibleNumbers := PencilledNumbers[PencilImageLoopfield] loop parse, ConnectedCells[PencilImageLoopfield], "-" if (InStr(PencilledCells, a_loopfield) and num[a_loopfield] != 0) PencilledAndPossibleNumbers := StrReplace(PencilledAndPossibleNumbers, num[a_loopfield]) num1 := num[PencilImageLoopfield] loop parse, PencilledAndPossibleNumbers if (a_loopfield != num1 or LoopIndex = 1 and PencilImageIndex = 1) { num[PencilImageLoopfield] := a_loopfield ImageNumbers := "" loop parse, ImageCells, "-" ImageNumbers .= num[a_loopfield] ImageNumbersS := SortPattern(ImageNumbers) if InValues(AllImages, ImageNumbersS) continue AllImages.push(ImageNumbersS) SetNumber(PencilImageLoopfield, a_loopfield) examined += 1 GuiCreateImage["examined"].text := examined if fill("minimum", 0, 0, 1) { loop parse, FillCells, "-" num[a_loopfield] := 0 fill("maximum", 0, 0, 1) loop parse, FillCells, "-" num[a_loopfield] := 0 fill("MyRandom", 0, 0, 1) loop parse, FillCells, "-" num[a_loopfield] := 0 differences := 0 loop parse, FillCells, "-" { if (minimum[a_loopfield] != maximum[a_loopfield]) differences += 1 if (minimum[a_loopfield] != MyRandom[a_loopfield]) differences += 1 if (maximum[a_loopfield] != MyRandom[a_loopfield]) differences += 1 } if (differences != 0) { if (GuiCreateImage["differences"].text != differences) GuiCreateImage["differences"].text := differences SortedImages[differences].push(ImageNumbers) MyCount[differences] += 1 if (BestCount.length < 4 or differences <= BestCount[4][1]) { BestCount := [] for d, n in MyCount if (n > 0) { BestCount.push([d, n]) if (BestCount.length = 4) break } BestCountText := "" for , dn in BestCount BestCountText .= dn[2] " x " dn[1] ", " BestCountText := RTrim(BestCountText, ", ") GuiCreateImage["BestCount"].text := BestCountText } } else { MainGui.Opt("-Disabled") SoundPlay "*48" for ctrl in GuiCreateImage if (ctrl.type = "text" and a_index > 4 and ctrl.name != "text4") { ctrl.text := "" ctrl.move(0, 0, 0, 0) } GuiCreateImage["text4"].move(,, 400, 80) GuiCreateImage["text4"].text := ( "Ready! You can improve the image by swapping numbers and by coloring, see View and ? in the menu." ) CancelButton.OnEvent("click", GuiCreateImageClose) button := GuiCreateImage.add("button", "x+20", "Save") button.OnEvent("click", CreateImageSave) LoopHighlight("") AllImages := "" SortedImages := "" if FileExist(a_desktop "\Sudoku\Create image") DirDelete a_desktop "\Sudoku\Create image", 1 HistoryPush return } } else { loop parse, FillCells, "-" num[a_loopfield] := 0 if not NotFilledContinued { SortedImages[4].push(ImageNumbers) ; Some changes don't get reached otherwise. NotFilledContinued := 1 } } if CancelWait CancelNow } SetNumber(PencilImageLoopfield, num1) } ImageNumbers := "" loop 200 if (SortedImages[a_index].length != 0) { ImageNumbers := SortedImages[a_index].pop() loop parse, ImageCells, "-" SetNumber(a_loopfield, SubStr(ImageNumbers, a_index, 1)) break } if (ImageNumbers = "") { MainGui.Opt("-Disabled") SoundPlay "*48" GuiCreateImage["text4"].move(,, 400) GuiCreateImage["text4"].text := GuiCreateImage["BestCount"].text for ctrl in GuiCreateImage if (ctrl.type = "text" and a_index > 4 and ctrl.name != "text3" and ctrl.name != "text4" and ctrl.name != "text6") { ctrl.text := "" ctrl.move(0, 0, 0, 0) } GuiCreateImage["text3"].move(,, 400) GuiCreateImage["text3"].text := "Cells with no unique solution in the best images:" GuiCreateImage["text6"].move(,, 400) GuiCreateImage["text6"].text := "ALL POSSIBLE CHANGES TRIED,`nNO SUDOKU WITH A UNIQUE SOLUTION FOUND." CancelButton.OnEvent("click", GuiCreateImageClose) LoopHighlight("") AllImages := "" SortedImages := "" if FileExist(a_desktop "\Sudoku\Create image") DirDelete a_desktop "\Sudoku\Create image", 1 HistoryPush return } } } CreateImageCancel(*) { global CancelWait := 1 } CancelNow(*) { global MainGui.Opt("-Disabled") GuiCreateImage.destroy() LoopHighlight("") SaveData AllImages := "" SortedImages := "" HistoryPush exit } CreateImageSave(*) { global GuiCreateImage.destroy() SaveAs } GuiCreateImageClose(*) { global GuiCreateImage.destroy() } ;---------------------------------------------------------------------------------------- ^+:: larger(*) { global MouseGetCell() ; returns rm and cm MainGui.hide SizeAndPosition(0.1) MainGui.show("w" wGui " h" wGui) if (rm != "" and cm != "") MouseClickCell(rm cm) } ;---------------------------------------------------------------------------------------- ^-:: smaller(*) { global MouseGetCell() MainGui.hide SizeAndPosition(-0.1) MainGui.show("w" wGui " h" wGui) if (rm != "" and cm != "") MouseClickCell(rm cm) } ;---------------------------------------------------------------------------------------- ^n:: NormalSize(*) { global MouseGetCell() MainGui.hide SizeAndPosition(1-zoom) MainGui.show("w" wGui " h" wGui) if (rm != "" and cm != "") MouseClickCell(rm cm) } ;---------------------------------------------------------------------------------------- ^c:: MixColor(*) { global if WinExist("Mix a color ... ahk_class AutoHotkeyGUI") { GuiMixColor.show return } GuiMixColor := gui() GuiMixColor.setfont("s12") GuiMixColor.add("text",, "Mix a color:") GuiMixColor.add("text", "xm", "Red:") slider1 := GuiMixColor.add("slider", "xp+60 vrDecimal AltSubmit range0-255") GuiMixColor.add("text", "xm", "Green:") slider2 := GuiMixColor.add("slider", "xp+60 vgDecimal AltSubmit range0-255") GuiMixColor.add("text", "xm", "Blue:") slider3 := GuiMixColor.add("slider", "xp+60 vbDecimal AltSubmit range0-255") GuiMixColor.add("text", "xm w80 h80 vMixColor", "g") GuiMixColor["MixColor"].setfont("s60 c000000", "Webdings") GuiMixColor.add("text", "x+20 w140 vMixValue") button1 := GuiMixColor.add("button", "xm default", "Set the color as &background color") button2 := GuiMixColor.add("button", "xm", "Set the color as &custom color") GuiMixColor.add("text", "xm", ( "Apply the custom color to a number or pencil mark with the C key. If there is more than one pencil mark in the cell, press the key repeatedly." )) slider1.OnEvent("change", AllSliders) slider2.OnEvent("change", AllSliders) slider3.OnEvent("change", AllSliders) button1.OnEvent("click", SetBackgroundColor) button2.OnEvent("click", SetCustomColor) GuiMixColor.OnEvent("size", GuiMixColorArrange) GuiMixColorArrange(GuiMixColor, MinMax, GuiMixColorWidth, GuiMixColorHeight) { ArrangeWindows(GuiMixColor, GuiMixColorWidth+10, GuiMixColorHeight+40) ; + borders, title bar and some space } GuiMixColor.OnEvent("escape", GuiMixColorClose) GuiMixColorClose(*) { GuiMixColor.destroy() } GuiMixColor.title := "Mix a color ..." GuiMixColor.show mix := 0 } AllSliders(MySlider, info) { global saved := GuiMixColor.Submit(0) mix := Format("{:X}", saved.rDecimal*16**4+saved.gDecimal*16**2+saved.bDecimal) if (mod(MySlider.value, 5) = 0 or info = 8) GuiMixColor["MixColor"].setfont("s60 c" mix, "Webdings") GuiMixColor["MixValue"].text := "0x" mix } SetBackgroundColor(*) { global if (mix != MainGui.BackColor) { cDefaultOld := cDefault background(mix) if (cDefault != cDefaultOld) { loop parse, AllCells, "-" { if (num[a_loopfield] != 0) SetNumber(a_loopfield, num[a_loopfield]) else loop 9 if (PencilMark[a_loopfield a_index] = 1) SetPencilMark(a_loopfield, a_index, 1) } HistoryPush } } } SetCustomColor(*) { global cCustom := mix } ;---------------------------------------------------------------------------------------- MySwitch(*) { global if (display = "colors") { display := "numbers" background(cBackgroundForNumbers) ViewMenu.Rename "5&", "S&witch from numbers to colors" } else { display := "colors" background(cBackgroundForColors) ViewMenu.Rename "5&", "S&witch from colors to numbers" } loop parse, AllCells, "-" { if (num[a_loopfield] != 0) SetNumber(a_loopfield, num[a_loopfield]) else loop 9 if (PencilMark[a_loopfield a_index] = 1) SetPencilMark(a_loopfield, a_index, 1) } HistoryPush } ;---------------------------------------------------------------------------------------- pgup:: back(*) { global if (HistoryIndex > 1) { HistoryIndex -= 1 StringToGui(history[HistoryIndex]) } } ;---------------------------------------------------------------------------------------- pgdn:: forward(*) { global if (HistoryIndex < history.length) { HistoryIndex += 1 StringToGui(history[HistoryIndex]) } } ;---------------------------------------------------------------------------------------- AllPencilMarks(*) { global AutoPencil := "all" SetAutoPencil HistoryPush } ;---------------------------------------------------------------------------------------- PencilMarkSinglesOrPairs(*) { global AutoPencil := "singles or pairs" SetAutoPencil HistoryPush } ;---------------------------------------------------------------------------------------- NoPencilMarks(*) { global AutoPencil := 0 for rcn, x in PencilMark if (x = 1) { rc := SubStr(rcn, 1, 2) n := SubStr(rcn, 3, 1) SetPencilMark(rc, n, 0) } HistoryPush } ;---------------------------------------------------------------------------------------- ClearBoard(*) { global MyString := display "/" cBackgroundFor%display% StringToGui(MyString) HistoryPush AutoPencil := 0 } ;---------------------------------------------------------------------------------------- ^pgdn:: SetOne(*) { global NoEmptyCell := 1 loop parse, AllCells, "-" if (num[a_loopfield] = 0) { NoEmptyCell := 0 break } if NoEmptyCell return tooltip if isSet(GuiExplain) GuiExplain.destroy() loop parse, AllCells, "-" highlight(a_loopfield, "") next := EliminatePossibleNumbers() MyText := "" KeepExplanationAndHighlightings := "" loop parse, AllCells, "-" KeepExplanationAndHighlightings .= a_loopfield num[a_loopfield] "-" if (next["action"] = "no possible number") { rc := next["rc"] highlight(rc, "red") MouseClickCell(rc) MyText := "This cell can't be filled." concat := "" loop 9 concat .= IfHasKey(explanation, rc a_index) if (concat != "") MyText := concat chr(8594) " " MyText if (SubStr(MyText, 1, 1) = chr(8594)) ; no explanations added MyText := SubStr(MyText, 3) } else if (next["action"] = "no possible cell") { u := next["u"] n := next["n"] loop parse, UnitCells[u], "-" if (num[a_loopfield] = 0) { highlight(a_loopfield, "red") MouseClickCell(a_loopfield) sleep 400 } MyText := n " can't be set anywhere in " unit[u] "." concat := "" loop parse, UnitCells[u], "-" concat .= IfHasKey(explanation, a_loopfield n) if (concat != "") MyText := concat chr(8594) " " MyText if (SubStr(MyText, 1, 1) = chr(8594)) MyText := SubStr(MyText, 3) } else if (next["action"] = "one possible number") { rc := next["rc"] n := next["n"] highlight(rc, "green") ; Highlight after set would reset n and cause a flicker. KeepExplanationAndHighlightings .= rc n ; Set before KeepExplanationAndHighlightings would delete KeepExplanationAndHighlightings. SetNumber(rc, n) MouseClickCell(rc) loop parse, ConnectedCells[rc], "-" SetPencilMark(a_loopfield, n, 0) SetAutoPencil HistoryPush MyText := n " is the only possible number in " rc "." concat := "" loop 9 if (a_index != n) concat .= IfHasKey(explanation, rc a_index) if (concat != "") MyText := concat chr(8594) " " MyText if (SubStr(MyText, 1, 1) = chr(8594)) MyText := SubStr(MyText, 3) } else if (next["action"] = "one possible cell") { u := next["u"] rc := next["rc"] n := next["n"] highlight(rc, "green") KeepExplanationAndHighlightings .= rc n SetNumber(rc, n) MouseClickCell(rc) loop parse, ConnectedCells[rc], "-" SetPencilMark(a_loopfield, n, 0) SetAutoPencil HistoryPush MyText := rc " is the only possible cell for " n " in " unit[u] "." concat := "" loop parse, UnitCells[u], "-" if (a_loopfield != rc) concat .= IfHasKey(explanation, a_loopfield n) if (concat != "") MyText := concat chr(8594) " " MyText if (SubStr(MyText, 1, 1) = chr(8594)) MyText := SubStr(MyText, 3) } else if (next["action"] = "several possible numbers") { rc := next["rc"] SeveralPossibleNumbers := next["n"] MouseClickCell(rc) ProvedNumbers := "" loop parse, SeveralPossibleNumbers { SetNumber(rc, a_loopfield) if fill("minimum", 0) ProvedNumbers .= a_loopfield HistoryPush back } if (StrLen(ProvedNumbers) = 0) { highlight(rc, "red") MyText := "This cell could be filled with " loop parse, SeveralPossibleNumbers MyText .= a_loopfield " or " MyText := SubStr(MyText, 1, -4) MyText .= ", but there is no solution with any of them." } else { if (StrLen(ProvedNumbers) = 1) { highlight(rc, "green") KeepExplanationAndHighlightings .= rc ProvedNumbers SetNumber(rc, ProvedNumbers) loop parse, ConnectedCells[rc], "-" SetPencilMark(a_loopfield, ProvedNumbers, 0) SetAutoPencil HistoryPush } else highlight(rc, "blue") MyText := "Possible numbers: " loop parse, SeveralPossibleNumbers MyText .= a_loopfield ", " MyText := SubStr(MyText, 1, -2) ". There is a solution with: " loop parse, ProvedNumbers MyText .= a_loopfield ", " MyText := SubStr(MyText, 1, -2) "." } } TextElements := [] loop parse, MyText, "$" TextElements.push(a_loopfield) ;---- omit repetitions ---- TextElements2 := [] for , element in TextElements if not InValues(TextElements2, element) TextElements2.push(element) TextElements := CopyArray(TextElements2) ;-------- TextElements := merge(TextElements, "n") TextElements := merge(TextElements, "rc") ;---- rc -> coord[rc], n -> ColorName[n], "number" -> "color" ---- MyText := "" for , element in TextElements MyText .= element "`n" MyText := RTrim(MyText, "`n") MyText2 := "" loopfield1 := "" loopfield2 := "" delimiter1 := "" delimiter2 := "" loop parse, MyText { if (a_loopfield = a_space or a_loopfield = "/" or a_loopfield = "." or a_loopfield = ":" or a_loopfield = "," or a_loopfield = ";" or a_loopfield = "`n" or a_loopfield = "`r") { delimiter1 := delimiter2 delimiter2 := a_loopfield if (loopfield2 = "") MyText2 .= delimiter2 else if IsDigit(loopfield2) { if (StrLen(loopfield2) = 1) { if (display = "colors") { if (delimiter1 = "" or delimiter1 = "`n" or delimiter1 = "`r" or delimiter1 = " " and loopfield1 = chr(8594)) MyText2 .= format("{:T}", ColorName[loopfield2]) delimiter2 else if (loopfield1 != "row" and loopfield1 != "column" and loopfield1 != "block") MyText2 .= ColorName[loopfield2] delimiter2 else MyText2 .= loopfield2 delimiter2 } else MyText2 .= loopfield2 delimiter2 } else if (StrLen(loopfield2) = 2) MyText2 .= coord[loopfield2] delimiter2 } else MyText2 .= loopfield2 delimiter2 loopfield1 := loopfield2 loopfield2 := "" } else loopfield2 .= a_loopfield } if (display = "colors") MyText2 := StrReplace(MyText2, "number", "color") MyText := MyText2 ;---- highlight cells ---- loop parse, AllCells, "-" if (highlighting[a_loopfield] != "green") if InStr(MyText, coord[a_loopfield] " ") highlight(a_loopfield, "red") else if InStr(MyText, coord[a_loopfield] " ") highlight(a_loopfield, "blue") MyText := StrReplace(MyText, " ") MyText := StrReplace(MyText, " ") ;---- wrap text ---- MyText2 := "" LineLenMax := 120 loop parse, MyText, "`n`r" if (a_loopfield != "") { line := a_loopfield if (StrLen(line) > LineLenMax) { line2 := "" Line2Len := 0 loop parse, line, a_space if (a_loopfield != "") { Line2Len += StrLen(a_loopfield) if (Line2Len > LineLenMax) { line2 .= "`n" a_loopfield Line2Len := 0 } else line2 .= " " a_loopfield } line := LTrim(line2) } MyText2 .= "`n" line } MyText := SubStr(MyText2, 2) ;---- tooltip or gui? ---- loop parse, MyText, "`n`r" LineCount := a_index if (LineCount <= 4) tooltip MyText else { GuiExplain := gui("-caption +border") GuiExplain.BackColor := "FFFFE1" GuiExplain.setfont("s12") GuiExplain.add("text",, MyText) GuiExplain.OnEvent("size", GuiExplainArrange) GuiExplainArrange(GuiExplain, MinMax, GuiExplainWidth, GuiExplainHeight) { ArrangeWindows(GuiExplain, GuiExplainWidth+5, GuiExplainHeight+5) ; + some space } GuiExplain.show } sleep 100 MainGui.show MouseClickCell(rm cm) } ;---------------------------------------------------------------------------------------- SetAll(*) { global MainGui.Opt("+OwnDialogs") loop parse, AllCells, "-" highlight(a_loopfield, "") if not fill("minimum") { HistoryPush msgbox "There is no solution!" } else if (bifurcation.length = 0) { HistoryPush msgbox "There is only one solution!" } else { HistoryPush sleep 1000 loop parse, FillCells, "-" num[a_loopfield] := 0 fill("maximum") differences := 0 loop parse, FillCells, "-" if (minimum[a_loopfield] != maximum[a_loopfield]) differences += 1 if (differences = 0) msgbox "There is only one solution!" else { HistoryPush msgbox "There is a second solution!" } } } ;---------------------------------------------------------------------------------------- help(*) { global if WinExist("? ahk_class AutoHotkeyGUI") { GuiHelp.show return } GuiHelp := gui() GuiHelp.setfont("s12") GuiHelp.add("text",, ( "Hotkeys: 1-9 Set numbers. Shift+1-9 Set/delete pencil marks. Esc Delete explanations, mouse menus and highlightings. Cancel 'Difficult'. Space bar Delete everything in a cell. Arrow keys Move the mouse cursor. P Set all possible pencil marks in a cell. S Swap numbers. R, G, B, D, C Color the just set number/pencil mark or (three seconds later) any number/pencil mark. If there is more than one pencil mark in a cell, press the key repeatedly. R, G, B, D, C = red, green, blue, default color, custom color (see 'Mix a color...' in the View menu). 'All pencil marks' and 'Pencil mark singles or pairs' in the Play menu highlight one or two possible cells in a row/column/block with red/green/blue pencil marks. Left mouse button Set/delete numbers. Right mouse button Set/delete pencil marks. Shift/Ctrl/Alt+left mouse button Highlight a cell/delete the highlighting. Shift/Ctrl/Alt+right mouse button Highlight all cells with the same number/delete the highlightings. See the menus for more hotkeys. 'Switch from numbers to colors' in the View menu REPLACES the nine numbers with nine colors:" )) GuiHelp.add("text", "xs+11", "1") loop 9 if (a_index > 1) GuiHelp.add("text", "xp+40", a_index) GuiHelp.add("text", "vcolor1 xs w30 h30", "=") loop 9 if (a_index > 1) GuiHelp.add("text", "vcolor" a_index " xp+40 w30 h30", "=") loop 9 GuiHelp["color" a_index].setfont("s24 c" colors[a_index][2], "Webdings") GuiHelp.add("text", "xs y+10", ( " The compiled program (that is, the .exe file but not the original .ahk file) also works with Wine in Linux, but without the font Webdings colors will appear as =, and highlightings will appear as g. To fix this, install ttf-mscorefonts-installer or copy Webdings.ttf from the Windows fonts folder (C:\Windows\Fonts) to the Linux truetype folder (probably /usr/share/fonts/truetype). Open the Linux fonts folder 'as Administrator'/'as Root'!" )) GuiHelp.OnEvent("size", GuiHelpArrange) GuiHelpArrange(GuiHelp, MinMax, GuiHelpWidth, GuiHelpHeight) { ArrangeWindows(GuiHelp, GuiHelpWidth+10, GuiHelpHeight+40) ; + borders, title bar and some space } GuiHelp.title := "?" GuiHelp.show } ;======================================================================================== ; Other hotkeys ;======================================================================================== ~Esc:: ; cancel { global tooltip if isSet(GuiExplain) GuiExplain.destroy() if isSet(NumList) { NumList.destroy() ClickNumList := 1 ; ClickNumList=1 in lbutton or A_TickCount-ClickNumList>3000 in rbutton break the loop. if InStr(context, "-rbutton") context := StrReplace(context, "-rbutton") } loop parse, AllCells, "-" highlight(a_loopfield, "") swap1 := "" CancelDifficultSudoku := 1 } ;---------------------------------------------------------------------------------------- r::SetColor("red") g::SetColor("green") b::SetColor("blue") d::SetColor("default") c:: { if isSet(cCustom) SetColor(cCustom) } ;---------------------------------------------------------------------------------------- p:: ; set all pencil marks { global if (MouseGetCell() and num[rm cm] = 0) { loop 9 { i := a_index valid := 1 loop parse, ConnectedCells[rm cm], "-" if (num[a_loopfield] = i) { valid := 0 break } if valid SetPencilMark(rm cm, i, 1) } HistoryPush } } ;---------------------------------------------------------------------------------------- s:: ; swap numbers { global if (MouseGetCell() and num[rm cm] != 0 and not InStr(FixedCells, rm cm)) { if (not isSet(swap1) or swap1 = "") { swap1 := num[rm cm] tooltip "Swap " item(swap1) " and ..." } else if (num[rm cm] = swap1) { swap1 := "" tooltip } else if (swap1 != "" and num[rm cm] != swap1) { swap2 := num[rm cm] loop parse, AllCells, "-" { if (num[a_loopfield] = swap1) SetNumber(a_loopfield, swap2) else if (num[a_loopfield] = swap2) SetNumber(a_loopfield, swap1) } HistoryPush swap1 := "" swap2 := "" tooltip } } } ;---------------------------------------------------------------------------------------- space:: ; delete everything in a cell { global if MouseGetCell() and not InStr(FixedCells, rm cm) { SetNumber(rm cm, 0, "default") loop 9 SetPencilMark(rm cm, a_index, 0, "default") SetAutoPencil HistoryPush } } ;---------------------------------------------------------------------------------------- 1:: ; set a number 2:: 3:: 4:: 5:: 6:: 7:: 8:: 9:: numpad1:: numpad2:: numpad3:: numpad4:: numpad5:: numpad6:: numpad7:: numpad8:: numpad9:: { global if MouseGetCell() and not InStr(FixedCells, rm cm) and (not isSet(NextInputWait) or not NextInputWait) { NextInputWait := 1 n := SubStr(ThisHotkey, -1) valid := 1 loop parse, ConnectedCells[rm cm], "-" if (num[a_loopfield] = n) { valid := 0 break } if valid { SetNumber(rm cm, n, "default") loop parse, ConnectedCells[rm cm], "-" SetPencilMark(a_loopfield, n, 0) SetAutoPencil LastAction := map("action", "number", "cell", rm cm, "n", n, "c", "", "time", A_TickCount) HistoryPush } else { SetNumber(rm cm, n) HistoryPush sleep 200 back history.pop() } NextInputWait := 0 } } ;---------------------------------------------------------------------------------------- +1:: ; set/delete a pencil mark +2:: +3:: +4:: +5:: +6:: +7:: +8:: +9:: +numpad1:: +numpad2:: +numpad3:: +numpad4:: +numpad5:: +numpad6:: +numpad7:: +numpad8:: +numpad9:: { global if (MouseGetCell() and num[rm cm] = 0 and (not isSet(NextInputWait) or not NextInputWait)) { NextInputWait := 1 n := SubStr(ThisHotkey, -1) if (PencilMark[rm cm n] = 0) { valid := 1 loop parse, ConnectedCells[rm cm], "-" if (num[a_loopfield] = n) { valid := 0 break } if valid { SetPencilMark(rm cm, n, 1) LastAction := map("action", "PencilMark", "cell", rm cm, "n", n, "c", "", "time", A_TickCount) HistoryPush } else { SetPencilMark(rm cm, n, 1) sleep 200 SetPencilMark(rm cm, n, 0) } } else { SetPencilMark(rm cm, n, 0, "default") HistoryPush } NextInputWait := 0 } } ;---------------------------------------------------------------------------------------- ~left:: ; move the mouse cursor ~right:: ~up:: ~down:: { global key := LTrim(ThisHotkey, "~") if (key = "left") { hor := -1 ver := 0 } else if (key = "right") { hor := 1 ver := 0 } else if (key = "up") { hor := 0 ver := -1 } else if (key = "down") { hor := 0 ver := 1 } if MouseGetCell() { if (cm + hor >= 1 and cm + hor <= 9) cm += hor if (rm + ver >= 1 and rm + ver <= 9) rm += ver } else { if (cm = "") cm := xm<=0 ? 1 : 9 if (rm = "") rm := ym<=0 ? 1 : 9 } MouseClickCell(rm cm) } ;======================================================================================== ; Mouse buttons ;======================================================================================== ~lbutton:: ; set numbers { global if InStr(context, "-rbutton") or InStr(context, "-EasySudoku") or InStr(context, "-DifficultSudoku") return if MouseGetCell() and not InStr(FixedCells, rm cm) { context .= "-lbutton" NumList := gui("+owner" MainGui.Hwnd " -caption +border") if (display = "numbers") NumList.setfont("s12") else NumList.setfont("s24") NumList.MarginX := 10 NumList.MarginY := 10 NumListLen := 0 loop 9 { n := a_index valid := 1 loop parse, ConnectedCells[rm cm], "-" if (num[a_loopfield] = n) { valid := 0 break } if valid { xplus := NumListLen=0 ? "" : " x+10" if (display = "numbers") NumList.add("text", "vNumListItem" a_index xplus, a_index).OnEvent("click", MouseSetNumber) else { NumList.add("text", "vNumListItem" a_index xplus " w30 h30", "=").OnEvent("click", MouseSetNumber) NumList["NumListItem" a_index].setfont("c" colors[n][2], "Webdings") } NumListLen += 1 } } if NumListLen > 0 { coordmode "mouse", "screen" mousegetpos &x1, &y1 coordmode "mouse", "client" NumList.OnEvent("size", NumListGetPos) NumListGetPos(NumList, MinMax, width, height) { NumListWidth := width } yNumList := display="numbers" ? y1-50 : y1-60 NumList.show("x" x1 " y" yNumList) ClickNumList := 0 loop 30 { sleep 100 if ClickNumList break coordmode "mouse", "screen" mousegetpos &x2, &y2 coordmode "mouse", "client" if (x2 > x1+NumListWidth+20 or x2 < x1-20 or y2 > y1+20 or y2 < yNumList-20) { NumList.destroy() break } } NumList.destroy() } context := StrReplace(context, "-lbutton") } } ;---------------------------------------------------------------------------------------- ~rbutton:: ; set pencil marks { global if InStr(context, "-lbutton") or InStr(context, "-EasySudoku") or InStr(context, "-DifficultSudoku") return if (MouseGetCell() and num[rm cm] = 0) { context .= "-rbutton" NumList := gui("+owner" MainGui.Hwnd " -caption +border") if (display = "numbers") NumList.setfont("s12") else NumList.setfont("s24") NumList.MarginX := 10 NumList.MarginY := 10 NumListLen := 0 loop 9 { n := a_index valid := 1 loop parse, ConnectedCells[rm cm], "-" if (num[a_loopfield] = n) { valid := 0 break } if valid { xplus := NumListLen=0 ? "" : " x+10" if (display = "numbers") NumList.add("text", "vNumListItem" a_index xplus, a_index).OnEvent("click", MouseSetPencilMark) else { NumList.add("text", "vNumListItem" a_index xplus " w30 h30", "=").OnEvent("click", MouseSetPencilMark) NumList["NumListItem" a_index].setfont("c" colors[n][2], "Webdings") } NumListLen += 1 } } if NumListLen > 0 { coordmode "mouse", "screen" mousegetpos &x1, &y1 coordmode "mouse", "client" NumList.OnEvent("size", NumListGetPos) NumListGetPos(NumList, MinMax, width, height) { NumListWidth := width } yNumList := display="numbers" ? y1-(ym-pos[rm])-30 : y1-(ym-pos[rm])-40 NumList.show("x" x1 " y" yNumList) ClickNumList := A_TickCount loop { sleep 100 if A_TickCount-ClickNumList > 3000 { NumList.destroy() break } coordmode "mouse", "screen" mousegetpos &x2, &y2 coordmode "mouse", "client" if (x2 > x1+NumListWidth+20 or x2 < x1-20 or y2 > y1+20 or y2 < yNumList-20) { NumList.destroy() break } } } context := StrReplace(context, "-rbutton") } } ;---------------------------------------------------------------------------------------- +lbutton::UserHighlight("red") ; highlight cells ^lbutton::UserHighlight("green") !lbutton::UserHighlight("blue") +rbutton::UserHighlightAllOfOne("red") ^rbutton::UserHighlightAllOfOne("green") !rbutton::UserHighlightAllOfOne("blue") ;======================================================================================== ; Subroutines A-Z ;======================================================================================== ArrangeWindows(win2, win2width, win2height) { global MainGui.GetPos(&win1left, &win1top, &win1width, &win1height) win1right := win1left+win1width win1bottom := win1top+win1height ScreenWidth := SysGet(16) ScreenHeight := SysGet(17) if (win1right+10+win2width < ScreenWidth) { if (win1top+win2height < ScreenHeight) win2.show("x" win1right+10 " y" win1top) else win2.show("x" win1right+10 " y" ScreenHeight-win2height) } else if (win1bottom+10+win2height < ScreenHeight) { if (win1left+win2width < ScreenWidth) win2.show("x" win1left " y" win1bottom+10) else win2.show("x" ScreenWidth-win2width " y" win1bottom+10) } else if (win1left-10-win2width > 0) { if (win1top+win2height < ScreenHeight) win2.show("x" win1left-10-win2width " y" win1top) else win2.show("x" win1left-10-win2width " y" ScreenHeight-win2height) } else if (win1top-10-win2height > 0) { if (win1left+win2width < ScreenWidth) win2.show("x" win1left " y" win1top-10-win2height) else win2.show("x" ScreenWidth-win2width " y" win1top-10-win2height) } else if (win1width+10+win2width < ScreenWidth) { MainGui.show("x" ScreenWidth-(win1width+10+win2width)) if (win1top+win2height < ScreenHeight) win2.show("x" ScreenWidth-win2width " y" win1top) else win2.show("x" ScreenWidth-win2width " y" ScreenHeight-win2height) } else if (win1height+10+win2height < ScreenHeight) { MainGui.show("y" ScreenHeight-(win1height+10+win2height)) if (win1left+win2width < ScreenWidth) win2.show("x" win1left " y" ScreenHeight-win2height) else win2.show("x" ScreenWidth-win2width " y" ScreenHeight-win2height) } else { ZoomFitWidth := zoom*(ScreenWidth-win2width-20)/wGui ZoomFitHeight := zoom*(ScreenHeight-win2height-(win1height-wGui)-20)/wGui if (ZoomFitWidth < 0.48 and ZoomFitHeight < 0.48) { MainGui.hide() SizeAndPosition(0.5-zoom) MainGui.show("y" 0 " w" wGui " h" wGui) win2.show("y" ScreenHeight-win2height) } else if (ZoomFitWidth > ZoomFitHeight) { MainGui.hide() SizeAndPosition(ZoomFitWidth-zoom) MainGui.show("x" 0 " w" wGui " h" wGui) if (win1top+win2height < ScreenHeight) win2.show("x" wGui+10 " y" win1top) else win2.show("x" wGui+10 " y" ScreenHeight-win2height) } else { MainGui.hide() SizeAndPosition(ZoomFitHeight-zoom) MainGui.show("y" 0 " w" wGui " h" wGui) if (win1left+win2width < ScreenWidth) win2.show("x" win1left " y" win1height+10) else win2.show("x" ScreenWidth-win2width " y" win1height+10) } } } ;---------------------------------------------------------------------------------------- ArrayAdd(array1, array2) { array1_2 := [] loop 2 for , element in array%a_index% if not InValues(array1_2, element) array1_2.push(element) return array1_2 } ;---------------------------------------------------------------------------------------- AutoColor(u) { if (u <= 9) return "red" else if (u <= 18) return "green" else return "blue" } ;---------------------------------------------------------------------------------------- background(color) { global if (MainGui.BackColor != color) { MainGui.BackColor := color cBackgroundFor%display% := color loop 6-StrLen(color) ; add leading zeros color := "0" . color red := "0x" . SubStr(color, 1, 2) ; split into red, green, blue green := "0x" . SubStr(color, 3, 2) blue := "0x" . SubStr(color, 5, 2) if (floor(sqrt(2*red**2+6*green**2+blue**2)) > 1.3*floor(sqrt(2*(255-red)**2+6*(255-green)**2+(255-blue)**2))) { cDefault := "202020" cFixed := "007000" } else { cDefault := "DFDFDF" cFixed := "FFE070" } loop parse, caption123, "-" MainGui["caption123" a_loopfield].setfont("c" cDefault) loop parse, captionABC, "-" MainGui["captionABC" a_loopfield].setfont("c" cDefault) } } ;---------------------------------------------------------------------------------------- ContinueEliminating(rc, n, index) { global local i, u, u2, rc2, n1, n2, n3, n_i, un, cell, cells ; ---- one possible number ---- if (StrLen(PossibleNumbers[rc]) = 1) { n2 := PossibleNumbers[rc] NextElements(index) IfThenElement[index] .= ", " rc " must be " n2 loop 9 if (a_index != n and a_index != n2) ; no other possible numbers ExplCollect(index, rc, a_index) ; ---- eliminate n2 in connected cells ---- NewEliminations := "" loop parse, ConnectedCells[rc], "-" if (not InStr(IfThenElement[index], a_loopfield) and not InStr(QueuedEliminations[index-1], a_loopfield n2) and num[a_loopfield] = 0 and InStr(PossibleNumbers[a_loopfield], n2)) { NewEliminations .= "-" a_loopfield AllEliminations[index] .= "-" a_loopfield n2 QueuedEliminations[index] .= "-" a_loopfield n2 } NewEliminations := LTrim(NewEliminations, "-") loop parse, NewEliminations, "-" { eliminate(a_loopfield, n2, 0) NextElements(index+1) IfThenElement[index+1] .= ", " a_loopfield " can't be " n2 IfThen[a_loopfield n2] := IfThenElement[index+1] ".`n" IfThenBefore[a_loopfield n2] := IfThenBeforeElement[index+1] ExplBefore[a_loopfield n2] := ExplBeforeElement[index+1] if (index = 2) ContinueEliminating(a_loopfield, n2, index+2) } } ; ---- one possible cell ---- for , u in InUnits(rc) if (PossibleCells[u n].length = 1 and not InStr(IfThenElement[index-1], PossibleCells[u n][1])) { rc2 := PossibleCells[u n][1] NextElements(index) IfThenElement[index] .= ", " rc2 " is the only possible cell for " n " in " unit[u] loop parse, UnitCells[u], "-" if (a_loopfield != rc and a_loopfield != rc2 and num[a_loopfield] = 0) ; no other possible cells ExplCollect(index, a_loopfield, n) ; ---- eliminate n in connected cells ---- NewEliminations := "" loop parse, ConnectedCells[rc2], "-" if (not InStr(UnitCells[u], a_loopfield) and not InStr(IfThenElement[index], a_loopfield) and not InStr(QueuedEliminations[index-1], a_loopfield n) and num[a_loopfield] = 0 and InStr(PossibleNumbers[a_loopfield], n)) { NewEliminations .= "-" a_loopfield AllEliminations[index] .= "-" a_loopfield n QueuedEliminations[index] .= "-" a_loopfield n } NewEliminations := LTrim(NewEliminations, "-") loop parse, NewEliminations, "-" { eliminate(a_loopfield, n, 0) NextElements(index+1) IfThenElement[index+1] .= ", " a_loopfield " can't be " n IfThen[a_loopfield n] := IfThenElement[index+1] ".`n" IfThenBefore[a_loopfield n] := IfThenBeforeElement[index+1] ExplBefore[a_loopfield n] := ExplBeforeElement[index+1] if (index = 2) ContinueEliminating(a_loopfield, n, index+2) } } ; ---- two possible numbers in rc ---- if (StrLen(PossibleNumbers[rc]) = 2) { n1 := SubStr(PossibleNumbers[rc], 1, 1) n2 := SubStr(PossibleNumbers[rc], 2, 1) NextElements(index) IfThenElement[index] .= ", " n1 " and " n2 " are the only possible numbers in " rc " " loop 9 if (a_index != n and a_index != n1 and a_index != n2) ; no other possible numbers ExplCollect(index, rc, a_index) ; ---- same two possible numbers in rc2 ---- loop parse, ConnectedCells[rc], "-" if (not InStr(IfThenElement[index], a_loopfield) and num[a_loopfield] = 0 and PossibleNumbers[a_loopfield] = PossibleNumbers[rc]) { rc2 := a_loopfield loop 9 if (a_index != n1 and a_index != n2) ; no other possible numbers ExplCollect(index, rc2, a_index) ; ---- eliminate both numbers in connected cells ---- NewEliminations1 := "" NewEliminations2 := "" loop parse, ConnectedCells[rc], "-" if (InStr(ConnectedCells[rc2], a_loopfield) and not InStr(IfThenElement[index], a_loopfield) and num[a_loopfield] = 0 and InStr(PossibleNumbers[a_loopfield], n1) and InStr(PossibleNumbers[a_loopfield], n2) and not InStr(QueuedEliminations[index-1], a_loopfield n1) and not InStr(QueuedEliminations[index-1], a_loopfield n2)) { NewEliminations1 .= "-" a_loopfield NewEliminations2 .= "-" a_loopfield AllEliminations[index] .= "-" a_loopfield n1 "-" a_loopfield n2 QueuedEliminations[index] .= "-" a_loopfield n1 "-" a_loopfield n2 } NewEliminations1 := LTrim(NewEliminations1, "-") NewEliminations2 := LTrim(NewEliminations2, "-") for i, n_i in [n1, n2] loop parse, NewEliminations%i%, "-" { eliminate(a_loopfield, n_i, 0) NextElements(index+1) IfThenElement[index+1] .= " and " rc2 " , " a_loopfield " can't be " n_i IfThen[a_loopfield n_i] := IfThenElement[index+1] ".`n" IfThenBefore[a_loopfield n_i] := IfThenBeforeElement[index+1] ExplBefore[a_loopfield n_i] := ExplBeforeElement[index+1] if (index = 2) ContinueEliminating(a_loopfield, n_i, index+2) } break } } ; ---- two possible cells for n ---- for , u in InUnits(rc) if (PossibleCells[u n].length = 2 and EmptyCells[u] > 2 and not InStr(IfThenElement[index-1], PossibleCells[u n][1]) and not InStr(IfThenElement[index-1], PossibleCells[u n][2])) { NextElements(index) IfThenElement[index] .= ", " PossibleCells[u n][1] " and " PossibleCells[u n][2] " are the only possible cells for " n loop parse, UnitCells[u], "-" if (a_loopfield != rc and a_loopfield != PossibleCells[u n][1] and a_loopfield != PossibleCells[u n][2] and num[a_loopfield] = 0) ; no other possible cells ExplCollect(index, a_loopfield, n) ; ---- same two possible cells for n2 ---- loop 9 if (a_index != n and PossibleCells.has(u a_index) and PossibleCells[u a_index].length = 2 and PossibleCells[u a_index][1] = PossibleCells[u n][1] and PossibleCells[u a_index][2] = PossibleCells[u n][2]) { n2 := a_index IfThenElement[index] .= " and " n2 " in " unit[u] loop parse, UnitCells[u], "-" if (a_loopfield != PossibleCells[u n2][1] and a_loopfield != PossibleCells[u n2][2] and num[a_loopfield] = 0) ; no other possible cells ExplCollect(index, a_loopfield, n2) ; ---- eliminate other numbers in the two possible cells ---- for , cell in PossibleCells[u n] { NewEliminations := "" loop parse, PossibleNumbers[cell] if (a_loopfield != n and a_loopfield != n2 and not InStr(QueuedEliminations[index-1], cell a_loopfield)) { NewEliminations .= "-" a_loopfield AllEliminations[index] .= "-" cell a_loopfield QueuedEliminations[index] .= "-" cell a_loopfield } NewEliminations := LTrim(NewEliminations, "-") loop parse, NewEliminations, "-" { n3 := a_loopfield eliminate(cell, n3, 0) NextElements(index+1) IfThenElement[index+1] .= ", " cell " can't be " n3 IfThen[cell n3] := IfThenElement[index+1] ".`n" IfThenBefore[cell n3] := IfThenBeforeElement[index+1] ExplBefore[cell n3] := ExplBeforeElement[index+1] if (index = 2) ContinueEliminating(cell, n3, index+2) } } break } } ; ---- two or three possible cells for n in u ---- for , u in InUnits(rc) if ((PossibleCells[u n].length = 2 or PossibleCells[u n].length = 3) and not InStr(IfThenElement[index-1], PossibleCells[u n][1]) and not InStr(IfThenElement[index-1], PossibleCells[u n][2]) and (PossibleCells[u n].length = 2 or not InStr(IfThenElement[index-1], PossibleCells[u n][3]))) { PossibleCellsText := PossibleCells[u n][1] " and " PossibleCells[u n][2] " " if (PossibleCells[u n].length = 3) PossibleCellsText .= " and " PossibleCells[u n][3] " " NextElements(index) IfThenElement[index] .= ", that leaves " PossibleCellsText " as possible cells for " n " in " unit[u] loop parse, UnitCells[u], "-" if (not InValues(PossibleCells[u n], a_loopfield) and num[a_loopfield] = 0) ; no other possible cells ExplCollect(index, a_loopfield, n) ; ---- all of them also in u2 ---- loop 27 if (a_index != u and InStr(UnitCells[a_index], PossibleCells[u n][1]) and InStr(UnitCells[a_index], PossibleCells[u n][2]) and (PossibleCells[u n].length = 2 or InStr(UnitCells[a_index], PossibleCells[u n][3]))) { u2 := a_index ; ---- eliminate n in other cells in u2 ---- NewEliminations := "" loop parse, UnitCells[u2], "-" if (not InStr(IfThenElement[index], a_loopfield) and not InStr(QueuedEliminations[index-1], a_loopfield n) and num[a_loopfield] = 0 and InStr(PossibleNumbers[a_loopfield], n)) { NewEliminations .= "-" a_loopfield AllEliminations[index] .= "-" a_loopfield n QueuedEliminations[index] .= "-" a_loopfield n } NewEliminations := LTrim(NewEliminations, "-") loop parse, NewEliminations, "-" { eliminate(a_loopfield, n, 0) NextElements(index+1) IfThenElement[index+1] .= ", " a_loopfield " can't be " n IfThen[a_loopfield n] := IfThenElement[index+1] ".`n" IfThenBefore[a_loopfield n] := IfThenBeforeElement[index+1] ExplBefore[a_loopfield n] := ExplBeforeElement[index+1] if (index = 2) ContinueEliminating(a_loopfield, n, index+2) } break } } } ;---------------------------------------------------------------------------------------- CopyArray(MyArray) { MyCopy := [] for , element in MyArray MyCopy.push(element) return MyCopy } ;---------------------------------------------------------------------------------------- CopyMap(MyMap, depth := 1) { MyCopy := map() for key, element1 in MyMap { if (depth = 1) MyCopy[key] := element1 else if (depth = 2) { MyArray := [] for , element2 in element1 MyArray.push(element2) MyCopy[key] := MyArray } } return MyCopy } ;---------------------------------------------------------------------------------------- eliminate(rc, n, checkout := 1) { global local i, u, cell PossibleNumbers[rc] := StrReplace(PossibleNumbers[rc], n) if (checkout and StrLen(PossibleNumbers[rc]) = 1) return map("rc", rc, "n", PossibleNumbers[rc], "action", "one possible number") for , u in InUnits(rc) for i, cell in PossibleCells[u n] if (cell = rc) { PossibleCells[u n].RemoveAt(i) if (checkout and PossibleCells[u n].length = 1) return map("u", u, "rc", PossibleCells[u n][1], "n", n, "action", "one possible cell") break } return map("action", "") } ;---------------------------------------------------------------------------------------- EliminatePossibleNumbers() { global ; Overview: ; get possible numbers for every cell ; get possible cells for every number in every unit ; possible numbers and possible cells are step by step eliminated by the following loops and solving techniques: ; loop 9 ; { ; - two cells, two possible numbers ; - two numbers, two possible cells ; - three cells, three possible numbers ; - three possible numbers, three cells ; - two or three possible cells, in the same row or column AND block ; - X-wing ; - Empty rectangle ; - Y-wing chains ; - Swordfish/Jellyfish ; } explanation := map() ; explanation[rc n] will be a text string, explaining why n can't be set in cell rc. AllPossibleNumbers := GetAllPossibleNumbers() PossibleNumbers := CopyMap(AllPossibleNumbers) for rc, n in PossibleNumbers { if (StrLen(n) = 0) return map("rc", rc, "action", "no possible number") if (StrLen(n) = 1) return map("rc", rc, "n", n, "action", "one possible number") } PossibleCells := GetPossibleCells(PossibleNumbers) for un, cells in PossibleCells { u := SubStr(un, 1, -1) n := SubStr(un, -1) if (cells.length = 0) return map("u", u, "n", n, "action", "no possible cell") if (cells.length = 1) return map("u", u, "rc", cells[1], "n", n, "action", "one possible cell") } EmptyCells := [] EmptyCells.length := 27 loop 27 { u := a_index EmptyCells[u] := 0 loop parse, UnitCells[u], "-" if (num[a_loopfield] = 0) EmptyCells[u] += 1 } loop 9 { main_index := a_index CellsWith2PossibleNumbers := "" loop parse, AllCells, "-" if (num[a_loopfield] = 0 and StrLen(PossibleNumbers[a_loopfield]) = 2) CellsWith2PossibleNumbers .= a_loopfield "-" CellsWith2PossibleNumbers := RTrim(CellsWith2PossibleNumbers, "-") ;---- two cells with the same two possible numbers ---- loop parse, CellsWith2PossibleNumbers, "-" { cell1 := a_loopfield i := a_index loop parse, CellsWith2PossibleNumbers, "-" if (a_index > i and InStr(ConnectedCells[cell1], a_loopfield)) { cell2 := a_loopfield if (PossibleNumbers[cell1] = PossibleNumbers[cell2]) { n1 := SubStr(PossibleNumbers[cell1], 1, 1) n2 := SubStr(PossibleNumbers[cell1], 2, 1) loop parse, ConnectedCells[cell1], "-" if (InStr(ConnectedCells[cell2], a_loopfield) and num[a_loopfield] = 0) for , n in [n1, n2] if InStr(PossibleNumbers[a_loopfield], n) { rc := a_loopfield explanation[rc n] := n " can't be in " rc " because " n1 " and " n2 . " are the only possible numbers in " cell1 " and " cell2 " , so they must be there.$" concat := "" loop parse, cell1 "-" cell2, "-" loop 9 if (a_index != n1 and a_index != n2) ; no other possible numbers concat .= IfHasKey(explanation, a_loopfield a_index) if (concat != "") explanation[rc n] := concat explanation[rc n] next := eliminate(rc, n) if (next["action"] = "one possible number" or next["action"] = "one possible cell") return next } break } } } ;---- two numbers with the same two possible cells ---- for un1, cells1 in PossibleCells { u1 := SubStr(un1, 1, -1) n1 := SubStr(un1, -1) if (PossibleCells[u1 n1].length = 2 and EmptyCells[u1] > 2) for un2, cells2 in PossibleCells { u2 := SubStr(un2, 1, -1) n2 := SubStr(un2, -1) if (n2 != n1 and PossibleCells[u2 n2].length = 2 and EmptyCells[u2] > 2 and PossibleCells[u2 n2][1] = PossibleCells[u1 n1][1] and PossibleCells[u2 n2][2] = PossibleCells[u1 n1][2]) { for , cell in PossibleCells[u1 n1] loop parse, PossibleNumbers[cell] if (a_loopfield != n1 and a_loopfield != n2) { n3 := a_loopfield rc1 := PossibleCells[u1 n1][1] rc2 := PossibleCells[u1 n1][2] if (u2 = u1) explanation[cell n3] := "In " unit[u1] ", " n1 " and " n2 else explanation[cell n3] := n1 " in " unit[u1] " and " n2 " in " unit[u2] explanation[cell n3] .= " can only be in " rc1 " and " rc2 " , so they will be there, and other numbers can't be there.$" concat := "" loop parse, UnitCells[u1], "-" if (a_loopfield != rc1 and a_loopfield != rc2) concat .= IfHasKey(explanation, a_loopfield n1) loop parse, UnitCells[u2], "-" if (a_loopfield != rc1 and a_loopfield != rc2) concat .= IfHasKey(explanation, a_loopfield n2) if (concat != "") explanation[cell n3] := concat explanation[cell n3] next := eliminate(cell, n3) if (next["action"] = "one possible number" or next["action"] = "one possible cell") return next } break } } } ;---- three cells with together three possible numbers ---- loop 27 { u := a_index loop parse, UnitCells[u], "-" if (num[a_loopfield] = 0 and StrLen(PossibleNumbers[a_loopfield]) <= 3) { cell1 := a_loopfield i := a_index loop parse, UnitCells[u], "-" if (a_index > i and num[a_loopfield] = 0 and PossibleNumbersLen(cell1, a_loopfield) <= 3) { cell2 := a_loopfield i := a_index loop parse, UnitCells[u], "-" if (a_index > i and num[a_loopfield] = 0 and PossibleNumbersLen(cell1, cell2, a_loopfield) = 3) { cell3 := a_loopfield loop parse, UnitCells[u], "-" if (a_loopfield != cell1 and a_loopfield != cell2 and a_loopfield != cell3 and num[a_loopfield] = 0) for , n in [n1, n2, n3] if InStr(PossibleNumbers[a_loopfield], n) { rc := a_loopfield explanation[rc n] := n " can't be in " rc " because " n1 " and " n2 " and " n3 . " are the only possible numbers in " cell1 " and " cell2 " and " cell3 " " . ", so they must be there.$" concat := "" loop parse, cell1 "-" cell2 "-" cell3, "-" loop 9 if (a_index != n1 and a_index != n2 and a_index != n3) ; no other possible numbers concat .= IfHasKey(explanation, a_loopfield a_index) if (concat != "") explanation[rc n] := concat explanation[rc n] next := eliminate(rc, n) if (next["action"] = "one possible number" or next["action"] = "one possible cell") return next } } } } } ;---- three numbers with together three possible cells ---- if (main_index > 1) for un, cells1 in PossibleCells { u := SubStr(un, 1, -1) n1 := SubStr(un, -1) if (cells1.length <= 3 and EmptyCells[u] > 3) loop 9 { n2 := a_index if (n2 != n1 and PossibleCells.has(u n2) and (PossibleCells[u n2].length = 2 or PossibleCells[u n2].length = 3)) { cells2 := ArrayAdd(cells1, PossibleCells[u n2]) if (cells2.length = 3) loop 9 { n3 := a_index if (n3 != n1 and n3 != n2 and PossibleCells.has(u n3) and (PossibleCells[u n3].length = 2 or PossibleCells[u n3].length = 3)) { cells3 := ArrayAdd(cells2, PossibleCells[u n3]) if (cells3.length = 3) for , cell in cells3 loop parse, PossibleNumbers[cell] if (a_loopfield != n1 and a_loopfield != n2 and a_loopfield != n3) { n4 := a_loopfield CellsString := "" for , cell2 in cells3 CellsString .= cell2 "-" CellsString := sort(CellsString, "d-") rc1 := SubStr(CellsString, 1, 2) rc2 := SubStr(CellsString, 4, 2) rc3 := SubStr(CellsString, 7, 2) explanation[cell n4] := "In " unit[u] ", " n1 " and " n2 " and " n3 " must be in " rc1 " and " rc2 . " and " rc3 " . That's three cells for three numbers, so there can't be other numbers in these cells.$" concat := "" loop parse, UnitCells[u], "-" if (not InValues(cells3, a_loopfield) and num[a_loopfield] = 0) ; no other possible cells loop 3 concat .= IfHasKey(explanation, a_loopfield n%a_index%) if (concat != "") explanation[cell n4] := concat explanation[cell n4] next := eliminate(cell, n4) if (next["action"] = "one possible number" or next["action"] = "one possible cell") return next } } } } } } ;---- one number, two or three possible cells, all in the same row or column AND block ---- for un, cells in PossibleCells { u := SubStr(un, 1, -1) n := SubStr(un, -1) if (PossibleCells[u n].length = 2 or PossibleCells[u n].length = 3) loop 27 if (a_index != u and InStr(UnitCells[a_index], PossibleCells[u n][1]) and InStr(UnitCells[a_index], PossibleCells[u n][2]) and (PossibleCells[u n].length = 2 or InStr(UnitCells[a_index], PossibleCells[u n][3]))) { u2 := a_index loop parse, UnitCells[u2], "-" if (not InStr(UnitCells[u], a_loopfield) and num[a_loopfield] = 0 and InStr(PossibleNumbers[a_loopfield], n)) { rc := a_loopfield MyText := n " can't be in " rc " because in " unit[u] " it must be in " loop parse, UnitCells[u], "-" if (InStr(UnitCells[u2], a_loopfield) and num[a_loopfield] = 0 and InStr(AllPossibleNumbers[a_loopfield], n)) MyText .= a_loopfield " or " MyText := SubStr(MyText, 1, -4) explanation[rc n] := MyText ".$" concat := "" loop parse, UnitCells[u], "-" if not InStr(UnitCells[u2], a_loopfield) concat .= IfHasKey(explanation, a_loopfield n) if (concat != "") explanation[rc n] := concat explanation[rc n] next := eliminate(rc, n) if (next["action"] = "one possible number" or next["action"] = "one possible cell") return next } break } } ;---- X-wing ---- for un, cells in PossibleCells { u := SubStr(un, 1, -1) n := SubStr(un, -1) if (PossibleCells[u n].length = 2) { A := PossibleCells[u n][1] B := PossibleCells[u n][2] loop u-1 if (PossibleCells.has(a_index n) and PossibleCells[a_index n].length = 2) { u2 := a_index C := PossibleCells[u2 n][1] D := PossibleCells[u2 n][2] if (A != C and A != D and B != C and B != D) { for , pattern in [A B C D, B A C D, A B D C, B A D C] { uCell1 := SubStr(pattern, 1, 2) uCell2 := SubStr(pattern, 3, 2) u2Cell1 := SubStr(pattern, 5, 2) u2Cell2 := SubStr(pattern, 7) if (InStr(ConnectedCells[uCell1], u2Cell1) and not InStr(ConnectedCells[uCell2], u2Cell1) and not InStr(ConnectedCells[u2Cell2], uCell1)) { loop parse, ConnectedCells[uCell2], "-" if (InStr(ConnectedCells[u2Cell2], a_loopfield) and num[a_loopfield] = 0 and InStr(PossibleNumbers[a_loopfield], n)) { rc := a_loopfield explanation[rc n] := ( n " can't be in " rc " because of the 'X-wing' " u2Cell1 " - " u2Cell2 "/" uCell1 " - " uCell2 ": - If in " unit[u2] " " n " is in " u2Cell1 " , then in " unit[u] " it must be in " uCell2 " . - If in " unit[u] " " n " is in " uCell1 " , then in " unit[u2] " it must be in " u2Cell2 " . Either way " n " can't be in " rc " because " rc " is connected with both " uCell2 " and " u2Cell2 ".$" ) concat := "" loop parse, UnitCells[u], "-" if (a_loopfield != PossibleCells[u n][1] and a_loopfield != PossibleCells[u n][2]) concat .= IfHasKey(explanation, a_loopfield n) loop parse, UnitCells[u2], "-" if (a_loopfield != PossibleCells[u2 n][1] and a_loopfield != PossibleCells[u2 n][2]) concat .= IfHasKey(explanation, a_loopfield n) if (concat != "") explanation[rc n] := concat explanation[rc n] next := eliminate(rc, n) if (next["action"] = "one possible number" or next["action"] = "one possible cell") return next } } } } } } } ;---- Empty rectangle ---- for un, cells in PossibleCells { u := SubStr(un, 1, -1) n := SubStr(un, -1) if (u > 18 and PossibleCells[u n].length >= 3 and PossibleCells[u n].length <= 5) for i1, road1 in UnitRows[u] for i2, road2 in UnitColumns[u] if (i2 > i1) { crossroads := UnitCells[road1] "-" UnitCells[road2] AllInCrossroads := 1 for , cell in PossibleCells[u n] if not InStr(crossroads, cell) { AllInCrossroads := 0 break } if AllInCrossroads { InRoad1 := 0 InRoad2 := 0 for , cell in PossibleCells[u n] { if InStr(UnitCells[road1], cell) InRoad1 += 1 if InStr(UnitCells[road2], cell) InRoad2 += 1 } if (InRoad1 >= 2 and InRoad2 >= 2) { ;---- road1 ---- AllEliminations := [] AllEliminations.length := 9 QueuedEliminations := [] QueuedEliminations.length := 9 IfThen := map() IfThenElement := [] IfThenElement.length := 9 IfThenBefore := map() IfThenBeforeElement := [] IfThenBeforeElement.length := 9 ExplBefore := map() ExplBeforeElement := [] ExplBeforeElement.length := 9 PossibleNumbersCopy := CopyMap(PossibleNumbers) PossibleCellsCopy := CopyMap(PossibleCells, 2) AllEliminations[1] := "" loop parse, UnitCells[road1] "-" UnitCells[road2], "-" if (InStr(UnitCells[road1], a_loopfield) and not InStr(UnitCells[u], a_loopfield) or InStr(UnitCells[road2], a_loopfield) and InStr(UnitCells[u], a_loopfield) and not InStr(UnitCells[road1], a_loopfield)) if (num[a_loopfield] = 0 and InStr(PossibleNumbers[a_loopfield], n)) AllEliminations[1] .= "-" a_loopfield n AllEliminations[1] := LTrim(AllEliminations[1], "-") QueuedEliminations[1] := AllEliminations[1] loop parse, AllEliminations[1], "-" { rc := SubStr(a_loopfield, 1, 2) eliminate(rc, n, 0) IfThenElement[1] := "If " n " is in " unit[u] " and " unit[road1] ", then " rc " can't be " n IfThen[rc n] := IfThenElement[1] ".`n" IfThenBeforeElement[1] := "" ExplBeforeElement[1] := "" ContinueEliminating(rc, n, 2) } AllEliminations1 := "" loop 9 AllEliminations1 .= IfHasKey(AllEliminations, a_index) AllEliminations1 := LTrim(AllEliminations1, "-") IfThen1 := CopyMap(IfThen) IfThenBefore1 := CopyMap(IfThenBefore) ExplBefore1 := CopyMap(ExplBefore) PossibleNumbers := CopyMap(PossibleNumbersCopy) PossibleCells := CopyMap(PossibleCellsCopy, 2) ;---- road2 ---- AllEliminations := [] AllEliminations.length := 9 QueuedEliminations := [] QueuedEliminations.length := 9 IfThen := map() IfThenElement := [] IfThenElement.length := 9 IfThenBefore := map() IfThenBeforeElement := [] IfThenBeforeElement.length := 9 ExplBefore := map() ExplBeforeElement := [] ExplBeforeElement.length := 9 PossibleNumbersCopy := CopyMap(PossibleNumbers) PossibleCellsCopy := CopyMap(PossibleCells, 2) AllEliminations[1] := "" loop parse, UnitCells[road2] "-" UnitCells[road1], "-" if (InStr(UnitCells[road2], a_loopfield) and not InStr(UnitCells[u], a_loopfield) or InStr(UnitCells[road1], a_loopfield) and InStr(UnitCells[u], a_loopfield) and not InStr(UnitCells[road2], a_loopfield)) if (num[a_loopfield] = 0 and InStr(PossibleNumbers[a_loopfield], n)) AllEliminations[1] .= "-" a_loopfield n AllEliminations[1] := LTrim(AllEliminations[1], "-") QueuedEliminations[1] := AllEliminations[1] loop parse, AllEliminations[1], "-" { rc := SubStr(a_loopfield, 1, 2) eliminate(rc, n, 0) IfThenElement[1] := "If " n " is in " unit[u] " and " unit[road2] ", then " rc " can't be " n IfThen[rc n] := IfThenElement[1] ".`n" IfThenBeforeElement[1] := "" ExplBeforeElement[1] := "" ContinueEliminating(rc, n, 2) } AllEliminations2 := "" loop 9 AllEliminations2 .= IfHasKey(AllEliminations, a_index) AllEliminations2 := LTrim(AllEliminations2, "-") IfThen2 := CopyMap(IfThen) IfThenBefore2 := CopyMap(IfThenBefore) ExplBefore2 := CopyMap(ExplBefore) PossibleNumbers := CopyMap(PossibleNumbersCopy) PossibleCells := CopyMap(PossibleCellsCopy, 2) ;---- road1 + road2 ---- TextCrossRoads := "" for , cell in PossibleCells[u n] TextCrossRoads .= " or " cell " " TextCrossRoads := SubStr(TextCrossRoads, 5) ; Concatenate TextCrossRoads before loop parse, AllEliminations1. ; Loop parse, AllEliminations1 might eliminate cells in PossibleCells[u n]. loop parse, AllEliminations1, "-" if InStr(AllEliminations2, a_loopfield) { EliminateWhat := SubStr(a_loopfield, 3, 1) EliminateIn := SubStr(a_loopfield, 1, 2) explanation[EliminateIn EliminateWhat] := EliminateWhat " can't be in " EliminateIn " because of the 'Empty rectangle' in " unit[u] ": " . "In " unit[u] ", " n " must be in " TextCrossRoads ", that is, in " unit[road1] " or " unit[road2] ".`n" loop 2 explanation[EliminateIn EliminateWhat] .= IfHasKey(IfThenBefore%a_index%, EliminateIn EliminateWhat) IfHasKey(IfThen%a_index%, EliminateIn EliminateWhat) MergeIfThen(6) ; MergeIfThen(6) still allows MergeIfThen(4) etc. MergeIfThen(4) MergeIfThen(2) explanation[EliminateIn EliminateWhat] .= "$" concat := "" loop parse, UnitCells[u], "-" if (num[a_loopfield] = 0 and not InStr(TextCrossRoads, a_loopfield)) ; no other possible cells concat .= IfHasKey(explanation, a_loopfield n) explanation[EliminateIn EliminateWhat] := concat IfHasKey(ExplBefore1, EliminateIn EliminateWhat) IfHasKey(ExplBefore2, EliminateIn EliminateWhat) explanation[EliminateIn EliminateWhat] next := eliminate(EliminateIn, EliminateWhat) if (next["action"] = "one possible number" or next["action"] = "one possible cell") return next } } } } } ;---- Y-wing chains ---- if (main_index > 2) loop parse, CellsWith2PossibleNumbers, "-" { rc := a_loopfield n1 := SubStr(PossibleNumbers[rc], 1, 1) n2 := SubStr(PossibleNumbers[rc], 2, 1) ;---- wing1 ---- AllEliminations := [] AllEliminations.length := 9 QueuedEliminations := [] QueuedEliminations.length := 9 IfThen := map() IfThenElement := [] IfThenElement.length := 9 IfThenBefore := map() IfThenBeforeElement := [] IfThenBeforeElement.length := 9 ExplBefore := map() ExplBeforeElement := [] ExplBeforeElement.length := 9 PossibleNumbersCopy := CopyMap(PossibleNumbers) PossibleCellsCopy := CopyMap(PossibleCells, 2) AllEliminations[1] := "" loop parse, ConnectedCells[rc], "-" if (num[a_loopfield] = 0 and InStr(PossibleNumbers[a_loopfield], n1)) AllEliminations[1] .= "-" a_loopfield n1 AllEliminations[1] := LTrim(AllEliminations[1], "-") QueuedEliminations[1] := AllEliminations[1] loop parse, AllEliminations[1], "-" { rc2 := SubStr(a_loopfield, 1, 2) eliminate(rc2, n1, 0) IfThenElement[1] := "If " rc " is " n1 ", then " rc2 " can't be " n1 IfThen[rc2 n1] := IfThenElement[1] ".`n" IfThenBeforeElement[1] := "" ExplBeforeElement[1] := "" ContinueEliminating(rc2, n1, 2) } AllEliminations1 := "" loop 9 AllEliminations1 .= IfHasKey(AllEliminations, a_index) AllEliminations1 := LTrim(AllEliminations1, "-") IfThen1 := CopyMap(IfThen) IfThenBefore1 := CopyMap(IfThenBefore) ExplBefore1 := CopyMap(ExplBefore) PossibleNumbers := CopyMap(PossibleNumbersCopy) PossibleCells := CopyMap(PossibleCellsCopy, 2) ;---- wing2 ---- AllEliminations := [] AllEliminations.length := 9 QueuedEliminations := [] QueuedEliminations.length := 9 IfThen := map() IfThenElement := [] IfThenElement.length := 9 IfThenBefore := map() IfThenBeforeElement := [] IfThenBeforeElement.length := 9 ExplBefore := map() ExplBeforeElement := [] ExplBeforeElement.length := 9 PossibleNumbersCopy := CopyMap(PossibleNumbers) PossibleCellsCopy := CopyMap(PossibleCells, 2) AllEliminations[1] := "" loop parse, ConnectedCells[rc], "-" if (num[a_loopfield] = 0 and InStr(PossibleNumbers[a_loopfield], n2)) AllEliminations[1] .= "-" a_loopfield n2 AllEliminations[1] := LTrim(AllEliminations[1], "-") QueuedEliminations[1] := AllEliminations[1] loop parse, AllEliminations[1], "-" { rc2 := SubStr(a_loopfield, 1, 2) eliminate(rc2, n2, 0) IfThenElement[1] := "If " rc " is " n2 ", then " rc2 " can't be " n2 IfThen[rc2 n2] := IfThenElement[1] ".`n" IfThenBeforeElement[1] := "" ExplBeforeElement[1] := "" ContinueEliminating(rc2, n2, 2) } AllEliminations2 := "" loop 9 AllEliminations2 .= IfHasKey(AllEliminations, a_index) AllEliminations2 := LTrim(AllEliminations2, "-") IfThen2 := CopyMap(IfThen) IfThenBefore2 := CopyMap(IfThenBefore) ExplBefore2 := CopyMap(ExplBefore) PossibleNumbers := CopyMap(PossibleNumbersCopy) PossibleCells := CopyMap(PossibleCellsCopy, 2) ;---- wing1 + wing2 ---- loop parse, AllEliminations1, "-" if InStr(AllEliminations2, a_loopfield) { EliminateWhat := SubStr(a_loopfield, 3, 1) EliminateIn := SubStr(a_loopfield, 1, 2) explanation[EliminateIn EliminateWhat] := EliminateWhat " can't be in " EliminateIn " " . "because of the Y-wing chains starting with " n1 " and " n2 " in " rc " :`n" loop 2 explanation[EliminateIn EliminateWhat] .= IfHasKey(IfThenBefore%a_index%, EliminateIn EliminateWhat) IfHasKey(IfThen%a_index%, EliminateIn EliminateWhat) MergeIfThen(6) MergeIfThen(4) MergeIfThen(2) explanation[EliminateIn EliminateWhat] .= "$" concat := "" loop 9 if (a_index != n1 and a_index != n2) ; no other possible numbers in rc concat .= IfHasKey(explanation, rc a_index) explanation[EliminateIn EliminateWhat] := concat IfHasKey(ExplBefore1, EliminateIn EliminateWhat) IfHasKey(ExplBefore2, EliminateIn EliminateWhat) explanation[EliminateIn EliminateWhat] next := eliminate(EliminateIn, EliminateWhat) if (next["action"] = "one possible number" or next["action"] = "one possible cell") return next } } ;---- Swordfish/Jellyfish ---- loop 9 { n := a_index rows := [] rows.length := 4 columns := [] columns.length := 4 ; rows contains the rows of the fish, e.g. rows[1] = "1", rows[2] = "13", rows[3] = "134", rows[4] = "1348" ; columns contains the columns of the fish, e.g. columns[1] = "2", columns[2] = "23", columns[3] = "235", columns[4] = "2357" if FishRowColumn(1) return next rows := [] rows.length := 4 columns := [] columns.length := 4 if FishColumnRow(1) return next } if (main_index > 3) { change := 0 for rc, n in PossibleNumbers if (n != PossibleNumbers0[rc]) { change := 1 break } if not change break } if (main_index > 2) PossibleNumbers0 := CopyMap(PossibleNumbers) } MyMin := 10 for rc, numbers in AllPossibleNumbers if (StrLen(numbers) < MyMin) { next := map("rc", rc, "n", numbers, "action", "several possible numbers") if (StrLen(numbers) = 2) break MyMin := StrLen(numbers) } return next } ;---------------------------------------------------------------------------------------- ExplCollect(index, rc, n) { global if (IfThenBefore.has(rc n) and IfThenBefore[rc n] != "" and not InStr(IfThenBeforeElement[index], IfThenBefore[rc n])) IfThenBeforeElement[index] .= IfThenBefore[rc n] if (IfThen.has(rc n) and IfThen[rc n] != "" and not InStr(IfThenBeforeElement[index], IfThen[rc n])) IfThenBeforeElement[index] .= IfThen[rc n] if (ExplBefore.has(rc n) and ExplBefore[rc n] != "" and not InStr(ExplBeforeElement[index], ExplBefore[rc n])) ExplBeforeElement[index] .= ExplBefore[rc n] if (explanation.has(rc n) and explanation[rc n] != "" and not InStr(ExplBeforeElement[index], explanation[rc n])) ExplBeforeElement[index] .= explanation[rc n] } ;---------------------------------------------------------------------------------------- fill(FillWith, show := 1, CreateImageFillRandom := 0, CreateImageFillMinimumMaximum := 0) { global local rc, n if not CreateImageFillRandom and not CreateImageFillMinimumMaximum { FillCells := "" loop parse, AllCells, "-" if (num[a_loopfield] = 0) FillCells .= a_loopfield "-" FillCells := RTrim(FillCells, "-") } filled := "" FilledLen := 0 bifurcation := [] BifurcationCount := 0 PossibleNumbers := GetAllPossibleNumbers() if CreateImageFillRandom loop parse, PencilledCells, "-" if (num[a_loopfield] = 0) { rc := a_loopfield loop parse, PossibleNumbers[rc] if not InStr(PencilledNumbers[rc], a_loopfield) PossibleNumbers[rc] := StrReplace(PossibleNumbers[rc], a_loopfield) } PossibleCells := GetPossibleCells(PossibleNumbers) loop { GetNext if NoNext { if (FillWith = "minimum") { minimum := map() loop parse, FillCells, "-" minimum[a_loopfield] := num[a_loopfield] } else if (FillWith = "maximum") { maximum := map() loop parse, FillCells, "-" maximum[a_loopfield] := num[a_loopfield] } else if (FillWith = "MyRandom") { MyRandom := map() loop parse, FillCells, "-" MyRandom[a_loopfield] := num[a_loopfield] } return 1 } if none { if (bifurcation.length = 0) return 0 LastBifurcation := bifurcation.pop() FilledLen := LastBifurcation["FilledLen"] loop parse, RTrim(filled, "-"), "-" if (a_index > FilledLen) SetNumber(a_loopfield, 0, "", show) filled := SubStr(filled, 1, FilledLen*3) rc := LastBifurcation["rc"] n := LastBifurcation["n"] if (FillWith = "minimum") n := SubStr(n, 1, 1) else if (FillWith = "maximum") n := SubStr(n, -1) else if (FillWith = "MyRandom") { n_random := "" loop parse, n n_random .= a_loopfield "-" n_random := sort(n_random, "random d-") n := SubStr(n_random, 1, 1) } SetNumber(rc, n, "", show) PossibleNumbers := GetAllPossibleNumbers() PossibleCells := GetPossibleCells(PossibleNumbers) if (StrLen(LastBifurcation["n"]) > 1) bifurcation.push(map("FilledLen", FilledLen, "rc", rc, "n", StrReplace(LastBifurcation["n"], n))) } else if one { rc := one[1] n := one[2] SetNumber(rc, n, "", show) filled .= rc "-" FilledLen += 1 UpdatePossibleNumbersAndCells(rc, n) } else if (several.length > 0) { rc := several[1][1] n := several[1][2] if (FillWith = "minimum") n := SubStr(n, 1, 1) else if (FillWith = "maximum") n := SubStr(n, -1) else if (FillWith = "MyRandom") { n_random := "" loop parse, n n_random .= a_loopfield "-" n_random := sort(n_random, "random d-") n := SubStr(n_random, 1, 1) } SetNumber(rc, n, "", show) filled .= rc "-" FilledLen += 1 UpdatePossibleNumbersAndCells(rc, n) bifurcation.push(map("FilledLen", FilledLen, "rc", rc, "n", StrReplace(several[1][2], n))) BifurcationCount += 1 } } } ;---------------------------------------------------------------------------------------- FishRowColumn(RowsLen) { global local rc loop 9 { NextRow := a_index if (RowsLen = 1) { rows[1] := "" columns[1] := "" } else { rows[RowsLen] := rows[RowsLen-1] columns[RowsLen] := columns[RowsLen-1] } if ((RowsLen = 1 or NextRow > SubStr(rows[RowsLen], -1)) and PossibleCells.has(NextRow n) and PossibleCells[NextRow n].length >= 2) { for , rc in PossibleCells[NextRow n] if not InStr(columns[RowsLen], SubStr(rc, 2, 1)) columns[RowsLen] .= SubStr(rc, 2, 1) if (StrLen(columns[RowsLen]) <= 4) { rows[RowsLen] .= NextRow if (RowsLen >= 3 and StrLen(columns[RowsLen]) = RowsLen) { loop parse, columns[RowsLen] loop parse, UnitCells[a_loopfield+9], "-" if (not InStr(rows[RowsLen], SubStr(a_loopfield, 1, 1)) and num[a_loopfield] = 0 and InStr(PossibleNumbers[a_loopfield], n)) { rc := a_loopfield if (RowsLen = 3) fish := "'Swordfish'" else if (RowsLen = 4) fish := "'Jellyfish'" TextRows := "" loop parse, rows[RowsLen] TextRows .= a_loopfield "-" TextRows := RTrim(TextRows, "-") TextColumns := "" loop parse, columns[RowsLen] TextColumns .= letter[a_loopfield] "-" TextColumns := RTrim(TextColumns, "-") TextColumns := sort(TextColumns, "d-") TextPossibleCells := "" loop RowsLen loop parse, UnitCells[SubStr(rows[RowsLen], a_index, 1)], "-" if (num[a_loopfield] = 0 and InStr(columns[RowsLen], SubStr(a_loopfield, 2, 1)) and InStr(AllPossibleNumbers[a_loopfield], n)) TextPossibleCells .= a_loopfield " , " TextPossibleCells := RTrim(TextPossibleCells, ", ") explanation[rc n] := n " can't be in " rc " because of the " fish " in rows " TextRows "/columns " TextColumns ":`n" . "In rows " TextRows ", " n " can be in " TextPossibleCells ". That's all in columns " TextColumns ", and that's one " n " for each column, " . "so there can't be any more " n "s in these columns.$" concat := "" loop RowsLen loop parse, UnitCells[SubStr(rows[RowsLen], a_index, 1)], "-" if not InStr(columns[RowsLen], SubStr(a_loopfield, 2, 1)) ; no other columns concat .= IfHasKey(explanation, a_loopfield n) if (concat != "") explanation[rc n] := concat explanation[rc n] next := eliminate(rc, n) if (next["action"] = "one possible number" or next["action"] = "one possible cell") return next } } else if (RowsLen < 3 or main_index > 1 and RowsLen < 4) if FishRowColumn(RowsLen+1) return next } } } } FishColumnRow(ColumnsLen) { global local rc loop 9 { NextColumn := a_index if (ColumnsLen = 1) { columns[1] := "" rows[1] := "" } else { columns[ColumnsLen] := columns[ColumnsLen-1] rows[ColumnsLen] := rows[ColumnsLen-1] } if ((ColumnsLen = 1 or NextColumn > SubStr(columns[ColumnsLen], -1)) and PossibleCells.has(NextColumn+9 n) and PossibleCells[NextColumn+9 n].length >= 2) { for , rc in PossibleCells[NextColumn+9 n] if not InStr(rows[ColumnsLen], SubStr(rc, 1, 1)) rows[ColumnsLen] .= SubStr(rc, 1, 1) if (StrLen(rows[ColumnsLen]) <= 4) { columns[ColumnsLen] .= NextColumn if (ColumnsLen >= 3 and StrLen(rows[ColumnsLen]) = ColumnsLen) { loop parse, rows[ColumnsLen] loop parse, UnitCells[a_loopfield], "-" if (not InStr(columns[ColumnsLen], SubStr(a_loopfield, 2, 1)) and num[a_loopfield] = 0 and InStr(PossibleNumbers[a_loopfield], n)) { rc := a_loopfield if (ColumnsLen = 3) fish := "'Swordfish'" else if (ColumnsLen = 4) fish := "'Jellyfish'" TextColumns := "" loop parse, columns[ColumnsLen] TextColumns .= letter[a_loopfield] "-" TextColumns := RTrim(TextColumns, "-") TextRows := "" loop parse, rows[ColumnsLen] TextRows .= a_loopfield "-" TextRows := RTrim(TextRows, "-") TextRows := sort(TextRows, "d-") TextPossibleCells := "" loop ColumnsLen loop parse, UnitCells[SubStr(columns[ColumnsLen], a_index, 1)+9], "-" if (num[a_loopfield] = 0 and InStr(rows[ColumnsLen], SubStr(a_loopfield, 1, 1)) and InStr(AllPossibleNumbers[a_loopfield], n)) TextPossibleCells .= a_loopfield " , " TextPossibleCells := RTrim(TextPossibleCells, ", ") explanation[rc n] := n " can't be in " rc " because of the " fish " in columns " TextColumns "/rows " TextRows ":`n" . "In columns " TextColumns ", " n " can be in " TextPossibleCells ". That's all in rows " TextRows ", and that's one " n " for each row, " . "so there can't be any more " n "s in these rows.$" concat := "" loop ColumnsLen loop parse, UnitCells[SubStr(columns[ColumnsLen], a_index, 1)+9], "-" if not InStr(rows[ColumnsLen], SubStr(a_loopfield, 1, 1)) ; no other rows concat .= IfHasKey(explanation, a_loopfield n) if (concat != "") explanation[rc n] := concat explanation[rc n] next := eliminate(rc, n) if (next["action"] = "one possible number" or next["action"] = "one possible cell") return next } } else if (ColumnsLen < 3 or main_index > 1 and ColumnsLen < 4) if FishColumnRow(ColumnsLen+1) return next } } } } ;---------------------------------------------------------------------------------------- font(TextControl, color, size, font, weight := 400) { global if (cTextControl[TextControl] != color) MainGui[TextControl].setfont("c" color) if (sTextControl[TextControl] != size) MainGui[TextControl].setfont("s" size) if (wTextControl.has(TextControl) and wTextControl[TextControl] != weight) MainGui[TextControl].setfont("w" weight) if (fTextControl[TextControl] != font) MainGui[TextControl].setfont(, font) if InStr(context, "-highlight") MainGui[TextControl].setfont("s" size) if (cTextControl[TextControl] != color or sTextControl[TextControl] != size or wTextControl.has(TextControl) and wTextControl[TextControl] != weight or fTextControl[TextControl] != font or InStr(context, "-highlight")) { MainGui[TextControl].redraw() cTextControl[TextControl] := color sTextControl[TextControl] := size wTextControl[TextControl] := weight fTextControl[TextControl] := font } } ;---------------------------------------------------------------------------------------- GetAllPossibleNumbers() { global local rc AllPossibleNumbers := map() loop parse, AllCells, "-" if (num[a_loopfield] = 0) { rc := a_loopfield AllPossibleNumbers[rc] := "123456789" loop parse, ConnectedCells[rc], "-" if (num[a_loopfield] != 0) AllPossibleNumbers[rc] := StrReplace(AllPossibleNumbers[rc], num[a_loopfield]) } return AllPossibleNumbers } ;---------------------------------------------------------------------------------------- GetNext() { global local rc, n, un, cells NoNext := 0 none := 0 one := "" several := [] for rc, n in PossibleNumbers { if (StrLen(n) = 0) { none := 1 return } else if (StrLen(n) = 1) { one := [rc, n] return } else if (several.length = 0 or StrLen(n) < StrLen(several[1][2])) several.InsertAt(1, [rc, n]) } for un, cells in PossibleCells { if (cells.length = 0) { none := 1 return } else if (cells.length = 1) { one := [cells[1], SubStr(un, -1)] return } } if (several.length = 0) { NoNext := 1 return } } ;---------------------------------------------------------------------------------------- GetPossibleCells(PossibleNumbers) { global local u, n PossibleCells := map() loop 27 { u := a_index AlreadySet := "" loop parse, UnitCells[u], "-" if (num[a_loopfield] != 0) AlreadySet .= num[a_loopfield] loop 9 if not InStr(AlreadySet, a_index) { n := a_index PossibleCells[u n] := [] loop parse, UnitCells[u], "-" if (num[a_loopfield] = 0 and InStr(PossibleNumbers[a_loopfield], n)) PossibleCells[u n].push(a_loopfield) } } return PossibleCells } ;---------------------------------------------------------------------------------------- GuiToString() { global MyString := display "/" MainGui.BackColor "/" loop parse, AllCells, "-" if (num[a_loopfield] != 0) { MyString .= a_loopfield num[a_loopfield] if (display = "numbers" and cNumber[a_loopfield] != "default") MyString .= cNumber[a_loopfield] MyString .= "," } MyString := RTrim(MyString, ",") MyString .= "/" loop parse, AllCells, "-" if (num[a_loopfield] = 0) loop 9 if (PencilMark[a_loopfield a_index] = 1) { MyString .= a_loopfield a_index if (display = "numbers" and cPencilMark[a_loopfield a_index] != "default") MyString .= cPencilMark[a_loopfield a_index] MyString .= "," } MyString := RTrim(MyString, ",") MyString .= "/" FixedCells MyString := RTrim(MyString, "/") return MyString } ;---------------------------------------------------------------------------------------- highlight(rc, color, restore := 0) { global if (highlighting[rc] = color and not restore) return highlighting[rc] := color if (color = "") text("WhiteSquare" rc, "") else { font("WhiteSquare" rc, pale[color], sHighlight, "Webdings") text("WhiteSquare" rc, "g") } context .= "-highlight" ; for font() and text() if (num[rc] != 0) SetNumber(rc, num[rc]) else loop 9 if (PencilMark[rc a_index] = 1) SetPencilMark(rc, a_index, 1) context := StrReplace(context, "-highlight") } ;---------------------------------------------------------------------------------------- HistoryPush() { global MyString := GuiToString() if (MyString != history[HistoryIndex]) { if (history.length > HistoryIndex) loop history.length-HistoryIndex history.pop() history.push(MyString) HistoryIndex += 1 if (history.length > 99) { history.RemoveAt(1) HistoryIndex -= 1 } } } ;---------------------------------------------------------------------------------------- IfHasKey(MyMap, key) { if MyMap.has(key) return MyMap[key] } ;---------------------------------------------------------------------------------------- intersection(u, r1, r2, r3, c1, c2, c3) { global UnitCells[u] := "" loop parse, AllCells, "-" { rc := a_loopfield r := SubStr(rc, 1, 1) c := SubStr(rc, 2, 1) if ((r = r1 or r = r2 or r = r3) and (c = c1 or c = c2 or c = c3)) UnitCells[u] .= rc "-" } UnitCells[u] := RTrim(UnitCells[u], "-") UnitRows[u] := [r1, r2, r3] UnitColumns[u] := [c1+9, c2+9, c3+9] } ;---------------------------------------------------------------------------------------- InUnits(rc) { global return [SubStr(rc, 1, 1), SubStr(rc, 2, 1)+9, block[rc]] } ;---------------------------------------------------------------------------------------- InValues(array, var) { VarString := var . "x" for , value in array { ValueString := value . "x" ; "x" makes sure that var and value are compared as strings. if (VarString = ValueString) return 1 } } ;---------------------------------------------------------------------------------------- item(n) { global if (display = "colors") return ColorName[n] else return n } ;---------------------------------------------------------------------------------------- LoopHighlight(NextHighlightLoopfield) { global if (isSet(HighlightLoopfield) and HighlightLoopfield != "") { highlight(HighlightLoopfield, "") HighlightLoopfield := "" } if (NextHighlightLoopfield != "") { highlight(NextHighlightLoopfield, "orange") HighlightLoopfield := NextHighlightLoopfield } } ;---------------------------------------------------------------------------------------- merge(array1, MergeWhat) { global local rc, n, i1, i2, element1, element2 n := [] n.length := array1.length rc := [] rc.length := array1.length for i1, element1 in array1 loop parse, element1, a_space { if (a_index = 1) n[i1] := a_loopfield else if (a_index = 5) { rc[i1] := a_loopfield break } } array2 := [] skip := [] skip.length := array1.length for i1, element1 in array1 { if skip.has(i1) and skip[i1] continue n_merge := n[i1] rc_merge := rc[i1] " " for i2, element2 in array1 if (i2 > i1) { if (MergeWhat = "n") { MyString := StrReplace(array1[i2], n[i2] " can't be in " rc[i2], n[i1] " can't be in " rc[i2]) if InStr(MyString, "X-wing") MyString := StrReplace(MyString, n[i2] " is ", n[i1] " is ") else if InStr(MyString, "Empty rectangle") or InStr(MyString, "Y-wing") MyString := StrReplace(MyString, rc[i2] " can't be " n[i2], rc[i2] " can't be " n[i1]) if (MyString = array1[i1]) { skip[i2] := 1 n_merge .= " and " n[i2] } } else if (MergeWhat = "rc") { MyString := StrReplace(array1[i2], n[i2] " can't be in " rc[i2], n[i2] " can't be in " rc[i1]) if InStr(MyString, "X-wing") MyString := StrReplace(MyString, rc[i2] " is ", rc[i1] " is ") else if InStr(MyString, "Empty rectangle") or InStr(MyString, "Y-wing") MyString := StrReplace(MyString, rc[i2] " can't be " n[i2], rc[i1] " can't be " n[i2]) if (MyString = array1[i1]) { skip[i2] := 1 rc_merge .= " and " rc[i2] " " } } } if (MergeWhat = "n" and InStr(n_merge, " and ")) { array1[i1] := StrReplace(array1[i1], n[i1] " can't be in " rc[i1], n_merge " can't be in " rc[i1]) if InStr(array1[i1], "X-wing") { array1[i1]:= StrReplace(array1[i1], n[i1] " is ", n_merge " are ") array1[i1]:= StrReplace(array1[i1], " it ", " they ") } else if InStr(array1[i1], "Empty rectangle") or InStr(array1[i1], "Y-wing") array1[i1] := StrReplace(array1[i1], rc[i1] " can't be " n[i1], rc[i1] " can't be " n_merge) } else if (MergeWhat = "rc" and InStr(rc_merge, " and ")) { array1[i1] := StrReplace(array1[i1], n[i1] " can't be in " rc[i1], n[i1] " can't be in " rc_merge) array1[i1] := StrReplace(array1[i1], " ", "") if InStr(array1[i1], "X-wing") array1[i1]:= StrReplace(array1[i1], rc[i1] " is ", rc_merge " are ") else if InStr(array1[i1], "Empty rectangle") or InStr(array1[i1], "Y-wing") array1[i1] := StrReplace(array1[i1], rc[i1] " can't be " n[i1], rc_merge " can't be " n[i1]) } array2.push(array1[i1]) } return array2 } ;---------------------------------------------------------------------------------------- MergeIfThen(MergeIndex) { global local rc, n, IfThen1, IfThen2, i1, i2, element1, element2 IfThen1 := [] loop parse, explanation[EliminateIn EliminateWhat], "`n" if (a_loopfield != "" and not InValues(IfThen1, a_loopfield)) IfThen1.push(a_loopfield) NoMerge := [] NoMerge.length := IfThen1.length text1 := [] text1.length := IfThen1.length rc := [] rc.length := IfThen1.length n := [] n.length := IfThen1.length text2 := [] text2.length := IfThen1.length for i1, element1 in IfThen1 if (i1 > 1) { text1[i1] := "" rc[i1] := "" n[i1] := "" text2[i1] := "" loop parse, element1, "," i2 := a_index if (i2 < MergeIndex) NoMerge[i1] := 1 else loop parse, element1, "," { if (a_index < MergeIndex) text1[i1] .= a_loopfield "," else if (a_index = MergeIndex) { loop parse, a_loopfield, a_space { if (MergeIndex = 2) ; a_index 2 to 7 = then rc can't be n { if (a_index = 2) text1[i1] .= " " a_loopfield else if (a_index = 3 or a_index = 4) rc[i1] .= " " a_loopfield else if (a_index = 7) n[i1] := " " RTrim(a_loopfield, ".") } else ; a_index 2 to 6 = rc can't be n { if (a_index = 2 or a_index = 3) rc[i1] .= " " a_loopfield else if (a_index = 6) n[i1] := " " RTrim(a_loopfield, ".") } } if (i2 = MergeIndex) text2[i1] := "." } else text2[i1] .= "," a_loopfield } } IfThen2 := [] skip := [] skip.length := IfThen1.length for i1, element1 in IfThen1 { if (i1 = 1 or NoMerge.has(i1) and NoMerge[i1]) { IfThen2.push(element1) continue } if skip.has(i1) and skip[i1] continue merge_rc := rc[i1] merge_n := n[i1] for i2, element2 in IfThen1 if (i2 > i1 and text1[i1] = text1[i2] and (RTrim(text2[i1], ".") = "" or RTrim(text2[i2], ".") = "" or InStr(text2[i1], RTrim(text2[i2], ".")) or InStr(text2[i2], RTrim(text2[i1], ".")))) { skip[i2] := 1 if not InStr(merge_n, "-") and not InStr(merge_rc, rc[i2]) ; Don't merge rc and n at the same time. merge_rc .= "-" rc[i2] else if not InStr(merge_rc, "-") and not InStr(merge_n, n[i2]) merge_n .= "-" n[i2] if (RTrim(text2[i2], ".") != "" and not InStr(text2[i1], RTrim(text2[i2], "."))) text2[i1] := text2[i2] } if InStr(merge_rc, "-") { merge_rc := sort(merge_rc, "d-") merge_rc := StrReplace(merge_rc, "-", " and") } else if InStr(merge_n, "-") { merge_n := sort(merge_n, "d-") merge_n := StrReplace(merge_n, "-", " and") } IfThen2.push(text1[i1] merge_rc " can't be" merge_n text2[i1]) } explanation[EliminateIn EliminateWhat] := "" for , element2 in IfThen2 explanation[EliminateIn EliminateWhat] .= element2 "`n" } ;---------------------------------------------------------------------------------------- MouseClickCell(rc) { global rm := SubStr(rc, 1, 1) cm := SubStr(rc, 2, 1) x := pos[cm] + wWhiteSquare/2 y := pos[rm] + wWhiteSquare/2 click x, y, 0 } ;---------------------------------------------------------------------------------------- MouseGetCell() { global mousegetpos &xm, &ym cm := "" rm := "" loop 9 if (xm >= pos[a_index] and xm < pos[a_index+1]) { cm := a_index break } loop 9 if (ym >= pos[a_index] and ym < pos[a_index+1]) { rm := a_index break } if (cm != "" and rm != "") return 1 } ;---------------------------------------------------------------------------------------- MouseSetNumber(NumListItem, xxx) { global ClickNumList := 1 n := SubStr(NumListItem.name, -1) NumList.destroy() if (num[rm cm] != n) { SetNumber(rm cm, n) loop parse, ConnectedCells[rm cm], "-" SetPencilMark(a_loopfield, n, 0) SetAutoPencil LastAction := map("action", "number", "cell", rm cm, "n", n, "c", "", "time", A_TickCount) } else { SetNumber(rm cm, 0, "default") SetAutoPencil } HistoryPush } ;---------------------------------------------------------------------------------------- MouseSetPencilMark(NumListItem, xxx) { global ClickNumList := A_TickCount n := SubStr(NumListItem.name, -1) if (PencilMark[rm cm n] = 0) { SetPencilMark(rm cm, n, 1) LastAction := map("action", "PencilMark", "cell", rm cm, "n", n, "c", "", "time", A_TickCount) } else SetPencilMark(rm cm, n, 0, "default") HistoryPush } ;---------------------------------------------------------------------------------------- NextElements(index) { global IfThenElement[index] := IfThenElement[index-1] IfThenBeforeElement[index] := IfThenBeforeElement[index-1] ExplBeforeElement[index] := ExplBeforeElement[index-1] if not AllEliminations.has(index) AllEliminations[index] := "" QueuedEliminations[index] := QueuedEliminations[index-1] } ;---------------------------------------------------------------------------------------- NextPermutation(pattern, maximum) { global ; The patterns array started with the lower numbers, and each pattern is sorted numerically. ; => Permutating is done by increasing numbers, and the last numbers of each pattern are increased first. ReversePattern := "" ; in reverse order ... loop parse, pattern ReversePattern := a_loopfield ReversePattern FirstPossibleIncrease := "" loop parse, ReversePattern ; ... to loop higher numbers first if (a_loopfield < maximum-(a_index-1)) ; For a possible increase, the first digit of ReversePattern (= the last digit of pattern) must be < maximum, the second digit must be < maximum-1 etc. { FirstPossibleIncrease := StrLen(pattern)-(a_index-1) ; refers to pattern again, not to ReversePattern break } if (FirstPossibleIncrease = "") return "" ; => exit the loop for permutations permutation := "" loop parse, pattern { if (a_index < FirstPossibleIncrease) permutation .= a_loopfield else if (a_index = FirstPossibleIncrease) { increase := a_loopfield+1 permutation .= increase } else { increase += 1 permutation .= increase } } return permutation } ;---------------------------------------------------------------------------------------- PleaseWait() { global GuiPleaseWait := gui("+owner" MainGui.Hwnd " -caption") GuiPleaseWait.setfont("s" sNumber*3//5) GuiPleaseWait.add("text",, "Please wait ...") MainGui.GetPos(&x, &y, &width, &height) x := x+width//2-2*wWhiteSquare y := y+height//2-wWhiteSquare//2 GuiPleaseWait.show("x" x " y" y " NoActivate") } ;---------------------------------------------------------------------------------------- PossibleNumbersLen(cell1, cell2, cell3 := "") { global PossibleNumbersString := "" loop 3 if (cell%a_index% != "") loop parse, PossibleNumbers[cell%a_index%] if not Instr(PossibleNumbersString, a_loopfield) PossibleNumbersString .= a_loopfield "-" if (StrLen(PossibleNumbersString)/2 = 3) { PossibleNumbersString := sort(PossibleNumbersString, "d-") n1 := SubStr(PossibleNumbersString, 1, 1) n2 := SubStr(PossibleNumbersString, 3, 1) n3 := SubStr(PossibleNumbersString, 5, 1) } return StrLen(PossibleNumbersString)/2 } ;---------------------------------------------------------------------------------------- SaveData() { global if FileExist(a_desktop "\Sudoku\Create image") DirDelete a_desktop "\Sudoku\Create image", 1 DirCreate a_desktop "\Sudoku\Create image" FileAppend ImageString, a_desktop "\Sudoku\Create image\image.txt" CanceledImageString := GuiToString() FileAppend CanceledImageString, a_desktop "\Sudoku\Create image\CanceledImage.txt" AllImagesString := "" for , image in AllImages AllImagesString .= image "-" AllImagesString := RTrim(AllImagesString, "-") FileAppend AllImagesString, a_desktop "\Sudoku\Create image\AllImages.txt" loop 200 if (SortedImages[a_index].length != 0) { SortedImagesString := "" for , image in SortedImages[a_index] SortedImagesString .= image "-" SortedImagesString := RTrim(SortedImagesString, "-") FileAppend SortedImagesString, a_desktop "\Sudoku\Create image\SortedImages" a_index ".txt" } BestCountString := "" for , dn in BestCount BestCountString .= dn[1] "/" dn[2] "-" BestCountString := RTrim(BestCountString, "-") FileAppend BestCountString, a_desktop "\Sudoku\Create image\BestCount.txt" } ;---------------------------------------------------------------------------------------- SetAutoPencil() { global local u, rc, n, un, cells if not AutoPencil return AllPossibleNumbers := GetAllPossibleNumbers() AllPossibleCells := GetPossibleCells(AllPossibleNumbers) ;---- are there singles or pairs? ---- singles := 0 pairs := 0 for , cells in AllPossibleCells if (cells.length = 1) { singles := 1 break } else if (cells.length = 2) pairs := 1 if (AutoPencil = "singles or pairs" and not singles and not pairs) return ;---- reset PencilMark and cPencilMark ---- loop parse, AllCells, "-" if (num[a_loopfield] = 0) loop 9 { PencilMark[a_loopfield a_index] := 0 cPencilMark[a_loopfield a_index] := "default" } ;---- set PencilMark and cPencilMark ---- if singles { for un, cells in AllPossibleCells if (cells.length = 1) { u := SubStr(un, 1, -1) n := SubStr(un, -1) PencilMark[cells[1] n] := 1 if (AutoColor(u) = "red") cPencilMark[cells[1] n] := "red" else if (AutoColor(u) = "green" and cPencilMark[cells[1] n] != "red") cPencilMark[cells[1] n] := "green" else if (AutoColor(u) = "blue" and cPencilMark[cells[1] n] != "red" and cPencilMark[cells[1] n] != "green") cPencilMark[cells[1] n] := "blue" } for rc, n in AllPossibleNumbers if (StrLen(n) = 1) PencilMark[rc n] := 1 } else if pairs { for un, cells in AllPossibleCells if (cells.length = 2) { u := SubStr(un, 1, -1) n := SubStr(un, -1) loop 2 { PencilMark[cells[a_index] n] := 1 if (AutoColor(u) = "red") cPencilMark[cells[a_index] n] := "red" else if (AutoColor(u) = "green" and cPencilMark[cells[a_index] n] != "red") cPencilMark[cells[a_index] n] := "green" else if (AutoColor(u) = "blue" and cPencilMark[cells[a_index] n] != "red" and cPencilMark[cells[a_index] n] != "green") cPencilMark[cells[a_index] n] := "blue" } } for rc, n in AllPossibleNumbers if (StrLen(n) = 2) loop parse, n PencilMark[rc a_loopfield] := 1 } if (AutoPencil = "all") for rc, n in AllPossibleNumbers loop parse, n PencilMark[rc a_loopfield] := 1 ;---- apply PencilMark and cPencilMark ---- loop parse, AllCells, "-" if (num[a_loopfield] = 0) loop 9 SetPencilMark(a_loopfield, a_index, PencilMark[a_loopfield a_index], cPencilMark[a_loopfield a_index]) } ;---------------------------------------------------------------------------------------- SetColor(c) { global if not MouseGetCell() return if (isSet(LastAction) and LastAction["action"] = "number" and A_TickCount - LastAction["time"] < 3000) { SetNumber(LastAction["cell"], LastAction["n"], c) HistoryPush } else if (isSet(LastAction) and LastAction["action"] = "PencilMark" and A_TickCount - LastAction["time"] < 3000) { SetPencilMark(LastAction["cell"], LastAction["n"], 1, c) HistoryPush } else if (isSet(LastAction) and LastAction["action"] = "ColorPencilMark" and LastAction["c"] = c and A_TickCount - LastAction["time"] < 1000) { OtherPencilMarks := "" loop 9 if (a_index != LastAction["n"] and PencilMark[LastAction["cell"] a_index] = 1 and cPencilMark[LastAction["cell"] a_index] != c) OtherPencilMarks .= a_index loop parse, OtherPencilMarks if (SubStr(OtherPencilMarks, -1) < LastAction["n"] or a_loopfield > LastAction["n"]) { context .= "-color" back SetPencilMark(LastAction["cell"], a_loopfield, 1, c) context := StrReplace(context, "-color") LastAction["n"] := a_loopfield LastAction["time"] := A_TickCount HistoryPush break } } else if (num[rm cm] != 0) { SetNumber(rm cm, num[rm cm], c) HistoryPush } else loop 9 if (PencilMark[rm cm a_index] = 1 and cPencilMark[rm cm a_index] != c) { SetPencilMark(rm cm, a_index, 1, c) LastAction := map("action", "ColorPencilMark", "cell", rm cm, "n", a_index, "c", c, "time", A_TickCount) HistoryPush break } } ;---------------------------------------------------------------------------------------- SetNumber(rc, n, color := "", show := 1) { global if isSet(LastAction) and not InStr(context, "-color") LastAction["action"] := "" if (isSet(KeepExplanationAndHighlightings) and KeepExplanationAndHighlightings != "" and not InStr(KeepExplanationAndHighlightings, rc n)) { tooltip if isSet(GuiExplain) GuiExplain.destroy() loop parse, AllCells, "-" highlight(a_loopfield, "") KeepExplanationAndHighlightings := "" } num[rc] := n if (color != "") cNumber[rc] := color if not show return if (n = 0) text("number" rc, "") else { loop 9 SetPencilMark(rc, a_index, 0) if (display = "colors") { font("number" rc, colors[n][2], sColor, "Webdings") text("number" rc, "=") } else { if (highlighting[rc] != "") color := "202020" else if (cNumber[rc] = "default") { if InStr(FixedCells, rc) color := cFixed else color := cDefault } else color := cNumber[rc] font("number" rc, color, sNumber, "Arial") text("number" rc, n) } } } ;---------------------------------------------------------------------------------------- SetPencilMark(rc, p, value, color := "") { global if isSet(LastAction) and not InStr(context, "-color") LastAction["action"] := "" PencilMark[rc p] := value if (color != "") cPencilMark[rc p] := color if (value = 0) text("PencilMark" rc p, "") else if (value = 1) { if (display = "colors") { font("PencilMark" rc p, colors[p][2], sPencilColor, "Webdings") text("PencilMark" rc p, "=") } else { if (highlighting[rc] != "" and cPencilMark[rc p] = "default") color := "202020" else if (cPencilMark[rc p] = "default") color := cDefault else color := cPencilMark[rc p] weight := cPencilMark[rc p]="default" ? 400 : 700 font("PencilMark" rc p, color, sPencilNumber, "Arial", weight) text("PencilMark" rc p, p) } } } ;---------------------------------------------------------------------------------------- SizeAndPosition(plus := 0) { global ZoomMax := round(a_screenheight/960, 2) ; 12 squares should not be higher than the screen. if (plus = 0 and isSet(wGui) or plus < 0 and zoom < 0.52 or plus > 0 and zoom > ZoomMax-0.02) return wWhiteSquare := 80 sHighlight := 60 sGreySquare := 64 sNumber := 50 sColor := 54 wPencilMark := 22 sPencilNumber := 14 sPencilColor := 12 wCaption := 32 sCaption := 20 if (zoom+plus < 0.48) zoom := 0.48 else if (zoom+plus > ZoomMax+0.02) zoom := ZoomMax+0.02 else zoom += plus wWhiteSquare := floor(zoom*80) sHighlight := floor(zoom*60) sGreySquare := floor(zoom*64) sNumber := floor(zoom*50) sColor := floor(zoom*54) wPencilMark := floor(zoom*22) sPencilNumber := floor(zoom*14) sPencilColor := floor(zoom*12) wCaption := floor(zoom*32) sCaption := floor(zoom*20) if (zoom < 1) wLine := 1 else if (zoom < 1.5) wLine := 2 else if (zoom < 2) wLine := 3 else wLine := 4 pos := [] pos.length := 10 loop 10 { pos[a_index] := (a_index-1)*(wWhiteSquare+wLine) ; positions of rows/columns/captions relative to the gui if (a_index = 10) pos[a_index] += 3*wLine else if (a_index > 6) pos[a_index] += 2*wLine else if (a_index > 3) pos[a_index] += wLine } wGui := pos[10]+wCaption add1 := 0 add2 := 0 add3 := 0 loop 3 add%a_index% := (wWhiteSquare-3*wPencilMark)//2 + (a_index-1)*wPencilMark ; positions of pencil mark rows/columns relative to the cell xAdd := [add1, add2, add3, add1, add2, add3, add1, add2, add3] yAdd := [add1, add1, add1, add2, add2, add2, add3, add3, add3] loop parse, AllCells, "-" { ; The backgroundtrans option in the number and PencilMark text controls can cause double numbers/pencil marks. ; Movedraw after SetNumber() and SetPencilMark() to avoid this. if (num[a_loopfield] != 0) SetNumber(a_loopfield, num[a_loopfield]) else loop 9 if (PencilMark[a_loopfield a_index] = 1) SetPencilMark(a_loopfield, a_index, 1) if (highlighting[a_loopfield] != "") highlight(a_loopfield, highlighting[a_loopfield], 1) r := SubStr(a_loopfield, 1, 1) c := SubStr(a_loopfield, 2, 1) MainGui["GreySquare" a_loopfield].setfont("s" sGreySquare " c818181", "Webdings") MainGui["GreySquare" a_loopfield].move(pos[c], pos[r], pos[c+1]-pos[c], pos[r+1]-pos[r]) MainGui["GreySquare" a_loopfield].redraw() ; GreySquare is not always an exact square. MainGui["WhiteSquare" a_loopfield].move(pos[c], pos[r], wWhiteSquare, wWhiteSquare) MainGui["WhiteSquare" a_loopfield].redraw() MainGui["number" a_loopfield].move(pos[c], pos[r], wWhiteSquare, wWhiteSquare) MainGui["number" a_loopfield].redraw() loop 9 { MainGui["PencilMark" a_loopfield a_index].move(pos[c]+xAdd[a_index], pos[r]+yAdd[a_index], wPencilMark, wPencilMark) MainGui["PencilMark" a_loopfield a_index].redraw() } } loop parse, caption123, "-" { MainGui["caption123" a_loopfield].move(pos[10], pos[a_index]+wWhiteSquare*0.3, wCaption, wWhiteSquare) MainGui["caption123" a_loopfield].redraw() MainGui["caption123" a_loopfield].setfont("s" sCaption, "Arial") } loop parse, captionABC, "-" { MainGui["captionABC" a_loopfield].move(pos[a_index], pos[10], wWhiteSquare, wCaption) MainGui["captionABC" a_loopfield].redraw() MainGui["captionABC" a_loopfield].setfont("s" sCaption, "Arial") } return } ;---------------------------------------------------------------------------------------- SortPattern(MyString) { replace := map("1","","2","","3","","4","","5","","6","","7","","8","","9","") next := 1 MyStringS := "" loop parse, MyString { if (replace[a_loopfield] = "") { replace[a_loopfield] := next next += 1 } MyStringS .= replace[a_loopfield] } return MyStringS } ;---------------------------------------------------------------------------------------- StringToGui(string) { global local rc string1 := "" loop parse, AllCells, "-" string1 .= a_loopfield num[a_loopfield] "-" loop parse, AllCells, "-" { num[a_loopfield] := 0 cNumber[a_loopfield] := "default" loop 9 { PencilMark[a_loopfield a_index] := 0 cPencilMark[a_loopfield a_index] := "default" } } SubStr1 := "" SubStr2 := "" SubStr3 := "" SubStr4 := "" SubStr5 := "" loop parse, string, "/", "`n`r" SubStr%a_index% := a_loopfield if (display != SubStr1) { ViewMenu.Rename "5&", "S&witch from " SubStr1 " to " display display := SubStr1 } if (MainGui.BackColor != SubStr2) background(SubStr2) loop parse, SubStr3, "," { rc := SubStr(a_loopfield, 1, 2) num[rc] := SubStr(a_loopfield, 3, 1) if (StrLen(a_loopfield) > 3) cNumber[rc] := SubStr(a_loopfield, 4) } loop parse, SubStr4, "," { rc := SubStr(a_loopfield, 1, 2) p := SubStr(a_loopfield, 3, 1) PencilMark[rc p] := 1 if (StrLen(a_loopfield) > 3) cPencilMark[rc p] := SubStr(a_loopfield, 4) } if (FixedCells = "" and SubStr5 != "") { SudokuMenu.Rename "4&", "&Unfix" SudokuMenu.Add "&Unfix", unfix } else if (FixedCells != "" and SubStr5 = "") { SudokuMenu.Rename "4&", "&Fix" SudokuMenu.Add "&Fix", fix } FixedCells := SubStr5 loop parse, AllCells, "-" { SetNumber(a_loopfield, num[a_loopfield], cNumber[a_loopfield]) if (num[a_loopfield] = 0) loop 9 SetPencilMark(a_loopfield, a_index, PencilMark[a_loopfield a_index], cPencilMark[a_loopfield a_index]) } changes := 0 loop parse, AllCells, "-" if not InStr(string1, a_loopfield num[a_loopfield]) { changes += 1 if (changes > 1) { tooltip if isSet(GuiExplain) GuiExplain.destroy() loop parse, AllCells, "-" highlight(a_loopfield, "") break } } } ;---------------------------------------------------------------------------------------- text(TextControl, text) { global if (MainGui[TextControl].text != text or InStr(context, "-highlight")) MainGui[TextControl].text := text } ;---------------------------------------------------------------------------------------- UpdatePossibleNumbersAndCells(rc, n) { global local i, u, cell loop parse, PossibleNumbers[rc] if (a_loopfield != n) for , u in InUnits(rc) for i, cell in PossibleCells[u a_loopfield] if (cell = rc) PossibleCells[u a_loopfield].RemoveAt(i) PossibleNumbers.delete(rc) for , u in InUnits(rc) PossibleCells.delete(u n) loop parse, ConnectedCells[rc], "-" if (num[a_loopfield] = 0 and InStr(PossibleNumbers[a_loopfield], n)) { PossibleNumbers[a_loopfield] := StrReplace(PossibleNumbers[a_loopfield], n) for , u in InUnits(a_loopfield) if not InValues(InUnits(rc), u) for i, cell in PossibleCells[u n] if (cell = a_loopfield) PossibleCells[u n].RemoveAt(i) } } ;---------------------------------------------------------------------------------------- UserHighlight(color) { global if MouseGetCell() { if (highlighting[rm cm] != color) highlight(rm cm, color) else highlight(rm cm, "") } } ;---------------------------------------------------------------------------------------- UserHighlightAllOfOne(color) { global if (MouseGetCell() and num[rm cm] != 0) { AllHighlighted := 1 loop parse, AllCells, "-" if (num[a_loopfield] = num[rm cm] and highlighting[a_loopfield] != color) AllHighlighted := 0 if not AllHighlighted { loop parse, AllCells, "-" if (num[a_loopfield] = num[rm cm]) highlight(a_loopfield, color) else if (highlighting[a_loopfield] = color) highlight(a_loopfield, "") } else { loop parse, AllCells, "-" if (highlighting[a_loopfield] = color) highlight(a_loopfield, "") } } }