Solves every Sudoku, finds and explains X-wings, Y-wing chains, "empty rectangles", Swordfish, Jellyfish.
You can set your own pencil marks or let the program set pencil marks.
Cells can be highlighted, numbers, pencil marks and the background can be colored, or the nine numbers can be altogether replaced with nine colors, that is, you can play Sudoku with colors instead of numbers.
Code: Select all
#Requires AutoHotkey v1.1.33+
; https://www.autohotkey.com/boards/viewtopic.php?t=15291
; CONTENTS:
;
; Auto-execute section:
; Basic variables and arrays
; Main GUI = Sudoku board and menu
;
; Main GUI events
; Subroutines and hotkeys for the main GUI's menu, in the menu's order
; Other hotkeys for the main GUI
; Mouse buttons
;
; Main solving subroutines:
; fill
; GetPossibleNumbers/Cells
; ContinueEliminating
; Other subroutines A-Z
#NoEnv
#singleinstance off
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 := []
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 := []
loop, 9
{
i := a_index
UnitCells[i] := ""
loop, 9
UnitCells[i] .= i a_index "-"
UnitCells[i] := SubStr(UnitCells[i], 1, -1) ; rows
UnitCells[i+9] := ""
loop, 9
UnitCells[i+9] .= a_index i "-"
UnitCells[i+9] := SubStr(UnitCells[i+9], 1, -1) ; columns
}
UnitRows := [] ; blocks
UnitColumns := []
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)
number := {}
PencilMark := {}
block := {}
coord := {}
ConnectedCells := {}
loop, parse, AllCells, -
{
rc := a_loopfield
r := SubStr(rc, 1, 1)
c := SubStr(rc, 2, 1)
number[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], "-")
}
;---- colors ----
colors := []
colors[1] := ["white", "0xFFFFFF"]
colors[2] := ["yellow", "0xFFFF00"]
colors[3] := ["orange", "0xFF8040"]
colors[4] := ["red", "0xFF0000"]
colors[5] := ["purple", "0x800080"]
colors[6] := ["blue", "0x0000FF"]
colors[7] := ["light blue", "0x00FFFF"]
colors[8] := ["green", "0x00A000"]
colors[9] := ["black", "0x202020"]
; Without quote marks, 0xABCDEF 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 it is used in gui,font, only the last six digits would be used, that is, the color would be 0x259375.
ColorName := []
ColorNumber := {}
ColorValue := {}
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.
ColorValue[colors[a_index][1]] := colors[a_index][2] ; ColorValue["white"] = 0xFFFFFF, ColorValue["yellow"] = 0xFFFF00 etc.
}
;---- pale colors to highlight cells ----
HighlightValue := {red: "0xFFCCCC", green: "0xCCFFCC", blue: "0xCCCCFF", orange: "0xFFCCA0"}
;---- starting values ----
cNumber := {}
cPencilMark := {}
highlight := {}
loop, parse, AllCells, -
{
cNumber[a_loopfield] := "default"
loop, 9
cPencilMark[a_loopfield a_index] := "default"
}
display := "numbers"
background("white")
cBackgroundForColors := "0xFFEECC"
;========================================================================================
; 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: number[11] to number[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.) highlight[11] to highlight[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!
gui, 1:-DPIScale
loop, parse, AllCells, -
{
gui, 1:add, text, vGreySquare%a_loopfield% -background, g
gui, 1:add, text, vWhiteSquare%a_loopfield%
gui, 1:add, text, vnumber%a_loopfield% +center backgroundtrans
loop, 9
gui, 1:add, text, vPencilMark%a_loopfield%%a_index% +center backgroundtrans
}
caption123 := "1-2-3-4-5-6-7-8-9"
loop, parse, caption123, -
gui, 1:add, text, vcaption123%a_loopfield% +center, %a_loopfield%
captionABC := "A-B-C-D-E-F-G-H-I"
loop, parse, captionABC, -
gui, 1:add, text, vcaptionABC%a_loopfield% +center, %a_loopfield%
; Arrays for the parameters of the text controls' text:
tTextControl := {} ; text
cTextControl := {} ; color
sTextControl := {} ; size
wTextControl := {} ; weight
fTextControl := {} ; font
; 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.
menu, SudokuMenu, add, &Easy and symmetrical, EasyAndSymmetrical
menu, SudokuMenu, add, &Difficult but not symmetrical, DifficultButNotSymmetrical
menu, SudokuMenu, add, Super d&ifficult, SuperDifficult
menu, SudokuMenu, add, &Open..., open
menu, SudokuMenu, add, &Fix, fix
menu, SudokuMenu, add, &Save as..., SaveAs
menu, SudokuMenu, add ; separator line
menu, SudokuMenu, add, hut
menu, SudokuMenu, add, tree
menu, SudokuMenu, add, autumn tree, AutumnTree
menu, SudokuMenu, add, spiral
menu, SudokuMenu, add, heart
menu, SudokuMenu, add, smiley
menu, SudokuMenu, add, star
menu, SudokuMenu, add, Christmas tree, ChristmasTree
menu, SudokuMenu, add, crown
menu, SudokuMenu, add, sun
menu, SudokuMenu, add ; separator line
menu, SudokuMenu, add, Create image..., CreateImage
menu, ViewMenu, add, Larger +, larger
menu, ViewMenu, add, Smaller -, smaller
menu, ViewMenu, add, Normal size N, NormalSize
menu, ViewMenu, add, Mix a color... Ctrl+C, MixColor
menu, ViewMenu, add, S&witch from numbers to colors, switch
menu, PlayMenu, add, Back page up, back
menu, PlayMenu, add, Forward page down, forward
menu, PlayMenu, add, &All pencil marks, AllPencilMarks
menu, PlayMenu, add, &Pencil mark singles or pairs, PencilMarkSinglesOrPairs
menu, PlayMenu, add, &No pencil marks, NoPencilMarks
menu, PlayMenu, add, &Clear board, ClearBoard
menu, SolveMenu, add, Set and explain one Ctrl+page down, SetOne
menu, SolveMenu, add, Set &all, SetAll
menu, MenuBar, add, &Sudoku, :SudokuMenu
menu, MenuBar, add, &View, :ViewMenu
menu, MenuBar, add, &Play, :PlayMenu
menu, MenuBar, add, S&olve, :SolveMenu
menu, MenuBar, add, &?, ?
gui, 1:menu, MenuBar
menu, LeftMouseMenu, add
menu, RightMouseMenu, add
loop
{
WinTitle := "Sudoku " A_Index
if not WinExist(WinTitle)
break
}
GroupAdd, SudokuWindows, %WinTitle%
zoom := 1
SizeAndPosition()
gui, 1:show, w%wGui% h%wGui%, %WinTitle% ; GuiSize is launched
today := A_MM A_DD
if (today >= 1201 and today <= 1224)
gosub star
else if (today >= 1225 or today <= 0106)
gosub ChristmasTree
history := []
HistoryIndex := 0
history()
AutoPencil := 0
return
;========================================================================================
; Main GUI events
;========================================================================================
GuiSize:
wingetpos,,, winwidth, winheight, %WinTitle% ahk_class AutoHotkeyGUI
wBorder := (winwidth-a_guiwidth)/2
hTitleMenu := winheight-a_guiheight-wBorder
return
GuiClose:
gui, 1:+OwnDialogs
something := 0
loop, parse, AllCells, -
{
if (number[a_loopfield] != 0)
something += 1
else loop, 9
if (PencilMark[a_loopfield a_index] = 1)
something += 1
}
if something > 3
{
msgbox, 0x2003,, Do you want to save the current situation?
IfMsgBox, Yes
gosub SaveAs
IfMsgBox, Cancel
return
}
exitapp
;========================================================================================
; Subroutines and hotkeys for the main GUI's menu, in the menu's order
;========================================================================================
#ifwinactive ahk_group SudokuWindows ahk_class AutoHotkeyGUI
;---- generate an easy and symmetrical Sudoku -------------------------------------------
EasyAndSymmetrical:
gosub PleaseWait
gosub ClearBoard
fill("random")
RandomCells := AllCells
sort, RandomCells, random d-
AlreadyLooped := ""
loop, parse, RandomCells, -
if not InStr(AlreadyLooped, 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
AlreadyLooped .= "-" SymCells
loop, 8
number[sym%a_index%] := 0
context := "EasyAndSymmetrical"
fill("minimum")
context := ""
StillOneSolution := 0
if (bifurcation.length() = 0)
StillOneSolution := 1
else if (AllValues(minimum) = AllValues(random))
{
context := "EasyAndSymmetrical"
fill("maximum")
context := ""
if (AllValues(maximum) = AllValues(minimum))
StillOneSolution := 1
}
loop, parse, SymCells, -
if StillOneSolution
set(a_loopfield, 0)
else
number[a_loopfield] := random[a_loopfield]
loop, parse, AllCells, -
if not InStr(SymCells, a_loopfield)
number[a_loopfield] := reset[a_loopfield]
}
if (display = "numbers")
loop, parse, AllCells, -
if (number[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, "Ubuntu", "Arial")
}
gosub fix
history()
gui, 1:-disabled
gui, PleaseWait:destroy
return
;---- generate a difficult (but not symmetrical) Sudoku ---------------------------------
DifficultButNotSymmetrical:
gosub PleaseWait
gosub ClearBoard
fill("random")
RandomCells := AllCells
sort, RandomCells, random d-
loop, parse, RandomCells, -
{
if a_index < 4
{
set(a_loopfield, 0)
continue
}
NextOmit := a_loopfield
number[NextOmit] := 0
context := "DifficultButNotSymmetrical"
fill("minimum")
context := ""
StillOneSolution := 0
if (bifurcation.length() = 0)
StillOneSolution := 1
else if (AllValues(minimum) = AllValues(random))
{
context := "DifficultButNotSymmetrical"
fill("maximum")
context := ""
if (AllValues(maximum) = AllValues(minimum))
StillOneSolution := 1
}
if StillOneSolution
set(NextOmit, 0)
else
number[NextOmit] := random[NextOmit]
loop, parse, AllCells, -
if (a_loopfield != NextOmit)
number[a_loopfield] := reset[a_loopfield]
}
gosub fix
history()
gui, 1:-disabled
gui, PleaseWait:destroy
return
;---- generate a super difficult Sudoku -------------------------------------------------
SuperDifficult:
CancelSuperDifficult := 0
loop
{
gosub DifficultButNotSymmetrical
sleep 400
SuperDifficultString := GuiToString()
TotalLineCount := 0
SuperDifficult := 0
TryAndError := 0
loop, 40
{
if CancelSuperDifficult
exit
caller := "SuperDifficult"
gosub setone
caller := ""
sleep 100
if (next["action"] = "several possible numbers")
{
TryAndError := 1
break
}
else if not SuperDifficult
{
if (LineCount >= 3)
TotalLineCount += LineCount
if (LineCount >= 7 or TotalLineCount >= 20)
SuperDifficult := 1 ; No break, there might still be a TryAndError.
}
}
if SuperDifficult and not TryAndError
{
StringToGui(SuperDifficultString)
gosub fix
history()
return
}
}
;---- open ------------------------------------------------------------------------------
open:
gui, 1:+OwnDialogs
if not FileExist(a_desktop "\Sudoku")
FileCreateDir, %a_desktop%\Sudoku
FileSelectFile, Sudoku,, %a_desktop%\Sudoku\
if (errorlevel = 0)
{
fileread, string, %Sudoku%
StringToGui(string)
history()
AutoPencil := 0
}
return
;---- fix/unfix -------------------------------------------------------------------------
fix:
FixedCells := ""
loop, parse, AllCells, -
if (number[a_loopfield] != 0)
FixedCells .= "-" a_loopfield
FixedCells := LTrim(FixedCells, "-")
if (FixedCells != "")
{
loop, parse, FixedCells, -
if (cNumber[a_loopfield] = "default")
set(a_loopfield, number[a_loopfield])
menu, SudokuMenu, rename, 5&, &Unfix
menu, SudokuMenu, add, &Unfix, unfix
history()
}
return
unfix:
if (FixedCells != "")
{
copy := FixedCells
FixedCells := "" ; FixedCells influences set()
loop, parse, copy, -
if (cNumber[a_loopfield] = "default")
set(a_loopfield, number[a_loopfield])
menu, SudokuMenu, rename, 5&, &Fix
menu, SudokuMenu, add, &Fix, fix
history()
}
return
;---- save as ---------------------------------------------------------------------------
SaveAs:
gui, 1:+OwnDialogs
if not FileExist(a_desktop "\Sudoku")
FileCreateDir, %a_desktop%\Sudoku
FileSelectFile, Sudoku, S16, %a_desktop%\Sudoku\
if (errorlevel = 0)
{
string := GuiToString()
if not InStr(SubStr(Sudoku, -4), ".") ; if the user didn't write an extension
Sudoku .= ".txt"
if FileExist(Sudoku)
filedelete, %Sudoku%
fileappend, %string%, %Sudoku%
}
return
;---- hut -------------------------------------------------------------------------------
hut:
string := "numbers/FFFFFF/1528B5B37,2448B5B37,2618B603D,3348B5B37,3718B603D,4248B5B37,"
. "4818B603D,528A7815D,56778563A,579916946,584916946,622A7815D,6338B5B37,6498B5B37,66878563A,"
. "685A27757,723A7815D,7428A6440,769BC9B7A,778BC9B7A,786A27757,825A7815D,8468A6440,883A27757,"
. "929A7815D,9438A6440,982A27757"
StringToGui(string)
gosub fix
AutoPencil := 0
history()
return
;---- tree ------------------------------------------------------------------------------
tree:
string := "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(string)
gosub fix
AutoPencil := 0
history()
return
;---- autumn tree -----------------------------------------------------------------------
AutumnTree:
string := "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(string)
gosub fix
AutoPencil := 0
history()
return
;---- spiral ----------------------------------------------------------------------------
spiral:
string := "numbers/FFFFFF/2420024FF,257006DFF,26500B6FF,3322500FF,37900FFFF,4276E00FF,"
. "456FFB700,48400FFB7,525B700FF,543FFFF00,561FF6E00,58600FF6E,621FF00FF,662FF2500,68800FF25,"
. "733FF00B6,748FF006D,759FF0024,78224FF00,8756DFF00,967B6FF00"
StringToGui(string)
gosub fix
AutoPencil := 0
history()
return
;---- heart -----------------------------------------------------------------------------
heart:
string := "numbers/FFFFFF/226CC0000,233CC0000,277CC0000,282CC0000,311CC0000,348CC0000,"
. "364CC0000,395CC0000,414CC0000,456CC0000,492CC0000,519CC0000,591CC0000,621CC0000,688CC0000,"
. "738CC0000,776CC0000,841CC0000,862CC0000,957CC0000"
StringToGui(string)
gosub fix
AutoPencil := 0
history()
return
;---- smiley ----------------------------------------------------------------------------
smiley:
string := "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(string)
gosub fix
AutoPencil := 0
history()
return
;---- Christmas tree --------------------------------------------------------------------
ChristmasTree:
string := "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(string)
gosub fix
AutoPencil := 0
history()
return
;---- crown -----------------------------------------------------------------------------
crown:
string := "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(string)
gosub fix
AutoPencil := 0
history()
return
;---- sun -------------------------------------------------------------------------------
sun:
string := "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(string)
gosub fix
AutoPencil := 0
history()
return
;---- star ------------------------------------------------------------------------------
star:
string := "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(string)
gosub fix
AutoPencil := 0
history()
return
;---- create image ----------------------------------------------------------------------
CreateImage:
; CONTENTS:
;
; 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:
; loop
; {
; Fill PencilImage with pencilled (and possible) numbers.
; Start over when the second loop tried all found images and doesn't find any more images.
; Give up after three iterations.
; loop
; {
; loop PencilImage
; {
; Try all pencilled (and possible) numbers in a_loopfield.
; If the resulting image is already in AllImages, skip the number.
; Solve the Sudoku with fill("minimum") and fill("maximum").
; If the solutions are identical, then there is only one solution and the Sudoku is ready.
; Otherwise add the image to AllImages and SortedImages.
; }
; Take the best image from SortedImages and continue the second loop.
; If there are no SortedImages, break the second loop and start over in the first loop.
; }
; }
if GuiCreateImage
{
gui, CreateImage:show
return
}
if GuiPaintImage
{
gui, PaintImage:show
return
}
ContinueLastCreating := 0
if FileExist(a_desktop "\Sudoku\Create image\PencilImage.txt")
{
msgbox, 0x2104,, The last creating was canceled. Do you want to continue it?
IfMsgBox, Yes
{
fileread, PencilImage, %a_desktop%\Sudoku\Create image\PencilImage.txt
StringToGui(PencilImage)
history()
ContinueLastCreating := 1
gosub PaintImageButtonCreateImage
return
}
}
gosub unfix
AutoPencil := 0
loop, parse, AllCells, -
highlight(a_loopfield, "")
tooltip
context := "CreateImage_PaintImage"
gui, 1:-disabled
gui, PaintImage:font, s12
gui, PaintImage:add, text,,
(
Paint an image with pencil marks and %display%:
Set/delete numbers 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.
)
if (display = "colors")
{
gui, PaintImage:add, text, xs, % "1 = " ColorName[1]
gui, PaintImage:add, text, xp+120, % "2 = " ColorName[2]
gui, PaintImage:add, text, xp+120, % "3 = " ColorName[3]
gui, PaintImage:add, text, xp+120, % "4 = " ColorName[4]
gui, PaintImage:add, text, xp+120, % "5 = " ColorName[5]
gui, PaintImage:add, text, xs, % "6 = " ColorName[6]
gui, PaintImage:add, text, xp+120, % "7 = " ColorName[7]
gui, PaintImage:add, text, xp+120, % "8 = " ColorName[8]
gui, PaintImage:add, text, xp+120, % "9 = " ColorName[9]
}
gui, PaintImage:add, button, xs y+20, Create image
gui, PaintImage:add, button, x+20, Cancel
gui, PaintImage:show,, Paint image ...
GuiPaintImage := 1
ArrangeWindows("PaintImage")
return
PaintImageGuiSize:
PaintImageWidth := a_guiwidth
PaintImageHeight := a_guiheight+30 ; +30 for the title bar
return
PaintImageButtonCancel:
PaintImageGuiClose:
PaintImageGuiEscape:
gui, PaintImage:destroy
GuiPaintImage := 0
if (context = "CreateImage")
CancelWait := 1
else
context := ""
return
PaintImageButtonCreateImage:
HistoryIndex0 := HistoryIndex
AutoPencil := 0
gui, 1:+OwnDialogs
winactivate, %WinTitle%
context := "CreateImage"
image := ""
PresetImage := ""
PencilImage := ""
loop, parse, AllCells, -
{
if (number[a_loopfield] != 0)
image .= a_loopfield "-"
else loop, 9
if (PencilMark[a_loopfield a_index] = 1)
{
image .= a_loopfield "-"
break
}
}
image := RTrim(image, "-")
if (StrLen(image) < 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.)
context := "CreateImage_PaintImage"
return
}
loop, parse, image, -
if (number[a_loopfield] != 0)
PresetImage .= a_loopfield "-"
else
PencilImage .= a_loopfield "-"
PresetImage := RTrim(PresetImage, "-")
PencilImage := RTrim(PencilImage, "-")
if (PencilImage = "") ;---- No pencil marks? ----
{
msgbox,
(
There are no pencil marks.
There have to be pencil marks so that the Sudoku creator can try different %display%.
)
context := "CreateImage_PaintImage"
return
}
missing := "123456789" ; ---- Two numbers missing completely? ----
loop, parse, image, -
{
missing := StrReplace(missing, number[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."
context := "CreateImage_PaintImage"
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(image, 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."
context := "CreateImage_PaintImage"
return
}
}
}
}
loop, 27 ;---- Less than n possible numbers in n cells in one unit? ----
{
u := a_index
PencilCellsInU := []
loop, parse, % UnitCells[u], -
if InStr(PencilImage, 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 i, 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 i, 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.
context := "CreateImage_PaintImage"
return
}
pattern := NextPermutation(pattern, PencilCellsInU.length())
if (pattern = "")
break ; exit the loop for permutations
}
}
}
PencilledNumbers := {}
loop, parse, PencilImage, -
loop, 9
if (PencilMark[a_loopfield a_index] = 1)
PencilledNumbers[a_loopfield] .= a_index
AllImages := []
SortedImages := []
loop, 60
if (a_index >= 4)
SortedImages[a_index] := []
if ContinueLastCreating
{
fileread, AllImagesString, %a_desktop%\Sudoku\Create image\AllImages.txt
examined := 0
loop, parse, AllImagesString, -
{
AllImages.push(a_loopfield)
examined += 1
}
BestDifferences := ""
loop, 60
if (a_index >= 4)
{
SortedImagesIndex := a_index
fileread, SortedImagesString, %a_desktop%\Sudoku\Create image\SortedImages%SortedImagesIndex%.txt
if (BestDifferences = "" and SortedImagesString != "")
BestDifferences := SortedImagesIndex
loop, parse, SortedImagesString, -
SortedImages[SortedImagesIndex].push(a_loopfield)
}
}
CancelWait := 0
;---- Create a Sudoku from the image ----
loop
{
fill("random")
if not AnySolution
{
msgbox, There is no solution.
context := "CreateImage_PaintImage"
HistoryIndex := HistoryIndex0
StringToGui(history[HistoryIndex])
return
}
if (a_index = 1)
{
gui, PaintImage:destroy
GuiPaintImage := 0
gui, 1:+disabled
gui, CreateImage:font, s12
gui, CreateImage:add, text, vCreateImageText w500 r7
gui, CreateImage:add, button,, Cancel
gui, CreateImage:show,, Create image ...
GuiCreateImage := 1
ArrangeWindows("CreateImage")
}
else if (a_index = 4)
{
guicontrol, CreateImage:text, CreateImageText, CAN'T CREATE A SUDOKU WITH A UNIQUE SOLUTION.
context := ""
AllImages := ""
SortedImages := ""
FileRemoveDir, %a_desktop%\Sudoku\Create image, 1
return
}
gosub LeaveImage
if not ContinueLastCreating
{
examined := 0
BestDifferences := ""
}
loop
{
LoopIndex := a_index
CopyArray(PencilledNumbers, "PencilledAndPossibleNumbers", 1)
loop, parse, PencilImage, -
{
rc := a_loopfield
loop, parse, % ConnectedCells[rc], -
if (number[a_loopfield] != 0)
PencilledAndPossibleNumbers[rc] := StrReplace(PencilledAndPossibleNumbers[rc], number[a_loopfield])
}
loop, parse, PencilImage, -
{
PencilImageIndex := a_index
PencilImageLoopfield := a_loopfield
StartingValue := number[PencilImageLoopfield]
if (HighlightedLoopfield != "")
highlight(HighlightedLoopfield, "")
highlight(PencilImageLoopfield, "orange")
HighlightedLoopfield := PencilImageLoopfield
loop, parse, % PencilledAndPossibleNumbers[PencilImageLoopfield]
if (a_loopfield != StartingValue or LoopIndex = 1 and PencilImageIndex = 1)
{
set(PencilImageLoopfield, a_loopfield)
PencilImageString := ""
loop, parse, PencilImage, -
PencilImageString .= number[a_loopfield]
if InValues(AllImages, PencilImageString)
continue
examined += 1
fill("minimum")
if AnySolution
{
fill("maximum")
gosub LeaveImage
differences := 0
loop, parse, AllCells, -
if (maximum[a_loopfield] != minimum[a_loopfield])
differences += 1
if (differences = 0)
{
gui, 1:-disabled
guicontrol, CreateImage:text, CreateImageText,
(
Loops: %LoopIndex%
Examined images: %examined%
Ready!
You can improve the image by swapping numbers
and by coloring, see View and ? in the menu.
)
gui, CreateImage:add, button, x+20, Save
highlight(HighlightedLoopfield, "")
HighlightedLoopfield := ""
history()
context := ""
AllImages := ""
SortedImages := ""
FileRemoveDir, %a_desktop%\Sudoku\Create image, 1
return
}
else
{
AllImages.push(PencilImageString)
SortedImages[differences].push(PencilImageString)
guicontrol, CreateImage:text, CreateImageText,
(
Loop %LoopIndex%
Examined images: %examined%
Cells with no unique solution
- in the last examined image: %differences%
- in the best not yet looped image: %BestDifferences%
Please wait, this can take some time ...
)
}
}
if CancelWait
gosub CancelNow
}
set(PencilImageLoopfield, StartingValue)
}
PencilImageString := ""
loop, 60
if (a_index >= 4 and SortedImages[a_index].length() != 0)
{
PencilImageString := SortedImages[a_index].pop()
BestDifferences := a_index
loop, parse, PencilImage, -
set(a_loopfield, SubStr(PencilImageString, a_index, 1))
break
}
if (PencilImageString = "")
{
HistoryIndex := HistoryIndex0
StringToGui(history[HistoryIndex])
guicontrol, CreateImage:text, CreateImageText, Start over ...
sleep 2000
break
}
}
}
return
CreateImageGuiSize:
CreateImageWidth := a_guiwidth
CreateImageHeight := a_guiheight+30 ; +30 for the title bar
return
CreateImageButtonCancel:
CreateImageGuiClose:
; no CreateImageGuiEscape to avoid canceling by mistake
if (context = "CreateImage")
{
CancelWait := 1
return
}
CancelNow:
gui, 1:-disabled
gui, CreateImage:destroy
GuiCreateImage := 0
if (HighlightedLoopfield != "")
{
highlight(HighlightedLoopfield, "")
HighlightedLoopfield := ""
}
if (context = "CreateImage")
{
HistoryIndex := HistoryIndex0
StringToGui(history[HistoryIndex])
gosub CreateImageSave
}
context := ""
AllImages := ""
SortedImages := ""
exit
CreateImageButtonSave:
gui, CreateImage:destroy
GuiCreateImage := 0
gosub SaveAs
return
;---- larger ----------------------------------------------------------------------------
larger:
+::
NumpadAdd::
MouseGetCell() ; returns rm and cm
gui, 1:hide
SizeAndPosition(0.1)
gui, 1:show, w%wGui% h%wGui%
if (rm != "" and cm != "")
MouseClickCell(rm cm)
return
;---- smaller ---------------------------------------------------------------------------
smaller:
-::
NumpadSub::
MouseGetCell() ; returns rm and cm
gui, 1:hide
SizeAndPosition(-0.1)
gui, 1:show, w%wGui% h%wGui%
if (rm != "" and cm != "")
MouseClickCell(rm cm)
return
;---- normal size -----------------------------------------------------------------------
NormalSize:
N::
MouseGetCell() ; returns rm and cm
gui, 1:hide
SizeAndPosition(1-zoom)
gui, 1:show, w%wGui% h%wGui%
if (rm != "" and cm != "")
MouseClickCell(rm cm)
return
;---- mix color -------------------------------------------------------------------------
MixColor:
^c::
if (GuiMixColor = 1)
{
gui, MixColor:show
return
}
gui, MixColor:font, s12
gui, MixColor:add, Text,, Mix a color:
gui, MixColor:add, text, xm, Red:
gui, MixColor:add, slider, xp+60 vrDecimal gSlider AltSubmit range0-255
gui, MixColor:add, text, xm, Green:
gui, MixColor:add, slider, xp+60 vgDecimal gSlider AltSubmit range0-255
gui, MixColor:add, text, xm, Blue:
gui, MixColor:add, slider, xp+60 vbDecimal gSlider AltSubmit range0-255
gui, MixColor:font, s60 c000000, Webdings
gui, MixColor:add, text, xm w80 h80 vMixColor, g
gui, MixColor:font
gui, MixColor:font, s12
gui, MixColor:add, text, x+20 w140 vMixValue
Gui, MixColor:add, Radio, xm vRadios gRadios checked, Background color
Gui, MixColor:add, Radio, xm gRadios, Custom color for numbers and pencil marks
gui, MixColor:add, button, xm default, OK
gui, MixColor:add, button, x+20, Cancel
gui, MixColor:add, text, xm,
(
Apply the 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.
)
gui, MixColor:show,, Mix a color ...
GuiMixColor := 1
ArrangeWindows("MixColor")
return
MixColorGuiSize:
MixColorWidth := a_guiwidth
MixColorHeight := a_guiheight+30 ; +30 for the title bar
return
Slider:
gui, MixColor:Submit, NoHide
mix := Format("{:#X}", rDecimal*16**4+gDecimal*16**2+bDecimal)
gui, MixColor:font, s60 c%mix%, Webdings
GuiControl, font, MixColor
GuiControl, text, MixValue, %mix%
return
Radios:
gui, MixColor:Submit, NoHide
return
MixColorButtonOK:
if (Radios = 1)
{
if (mix != cBackground)
{
background(mix)
loop, parse, AllCells, -
{
if (number[a_loopfield] != 0)
set(a_loopfield, number[a_loopfield])
else loop, 9
if (PencilMark[a_loopfield a_index] = 1)
PencilMark(a_loopfield, a_index, 1)
}
history()
}
}
else
{
cCustom := "xyz" . mix
; Without "xyz", cCustom would be stored as decimal, and when it is used in gui,font, only the last six digits would be used.
; "xyz" will be removed by hex().
}
return
MixColorButtonCancel:
MixColorGuiClose:
MixColorGuiEscape:
gui, MixColor:destroy
GuiMixColor := 0
return
;---- switch from numbers to colors/from colors to numbers ------------------------------
switch:
if (display = "colors")
{
display := "numbers"
background(cBackgroundForNumbers)
menu, ViewMenu, rename, 5&, S&witch from numbers to colors
}
else
{
display := "colors"
background(cBackgroundForColors)
menu, ViewMenu, rename, 5&, S&witch from colors to numbers
}
loop, parse, AllCells, -
{
if (number[a_loopfield] != 0)
set(a_loopfield, number[a_loopfield])
else loop, 9
if (PencilMark[a_loopfield a_index] = 1)
PencilMark(a_loopfield, a_index, 1)
}
history()
return
;---- back ------------------------------------------------------------------------------
back:
pgup::
if (HistoryIndex > 1)
{
HistoryIndex -= 1
StringToGui(history[HistoryIndex])
}
return
;---- forward ---------------------------------------------------------------------------
forward:
pgdn::
if (HistoryIndex < history.MaxIndex())
{
HistoryIndex += 1
StringToGui(history[HistoryIndex])
}
return
;---- all pencil marks ------------------------------------------------------------------
AllPencilMarks:
AutoPencil := "AllPossibleNumbers"
AutoPencil()
history()
return
;---- pencil mark singles or pairs ------------------------------------------------------
PencilMarkSinglesOrPairs:
AutoPencil := "SinglesOrPairs"
AutoPencil()
history()
return
;---- no pencil marks -------------------------------------------------------------------
NoPencilMarks:
AutoPencil := 0
for rcn, x in PencilMark
if (x = 1)
{
rc := SubStr(rcn, 1, 2)
n := SubStr(rcn, 3, 1)
PencilMark(rc, n, 0)
}
history()
return
;---- clear board -----------------------------------------------------------------------
ClearBoard:
string := display "/" cBackgroundFor%display%
StringToGui(string)
history()
AutoPencil := 0
return
;---- set and explain one ---------------------------------------------------------------
SetOne:
^pgdn::
tooltip
gui, explain:destroy
loop, parse, AllCells, -
highlight(a_loopfield, "")
GetPossibleNumbers("SetOne")
gui, 1:-disabled
gui, PleaseWait:destroy
text := ""
KeepExplanationAndHighlightings := ""
loop, parse, AllCells, -
KeepExplanationAndHighlightings .= a_loopfield number[a_loopfield] "-"
if (next["action"] = "solved")
return
else if (next["action"] = "no possible number")
{
rc := next["rc"]
highlight(rc, "red")
MouseClickCell(rc)
text := "This cell can't be filled."
concat := ""
loop, 9
concat .= explanation[rc a_index]
if (concat != "")
text := concat chr(8594) " " text
if (SubStr(text, 1, 1) = chr(8594)) ; no explanations added
text := SubStr(text, 3)
}
else if (next["action"] = "no possible cell")
{
u := next["u"]
n := next["n"]
loop, parse, % UnitCells[u], -
if (number[a_loopfield] = 0)
{
highlight(a_loopfield, "red")
MouseClickCell(a_loopfield)
sleep 400
}
text := n " can't be set anywhere in " unit[u] "."
concat := ""
loop, parse, % UnitCells[u], -
concat .= explanation[a_loopfield n]
if (concat != "")
text := concat chr(8594) " " text
if (SubStr(text, 1, 1) = chr(8594))
text := SubStr(text, 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.
set(rc, n)
if (caller != "SuperDifficult")
MouseClickCell(rc)
loop, parse, % ConnectedCells[rc], -
PencilMark(a_loopfield, n, 0)
AutoPencil()
history()
text := n " is the only possible number in " rc "."
concat := ""
loop, 9
if (a_index != n)
concat .= explanation[rc a_index]
if (concat != "")
text := concat chr(8594) " " text
if (SubStr(text, 1, 1) = chr(8594))
text := SubStr(text, 3)
}
else if (next["action"] = "one possible cell")
{
u := next["u"]
rc := next["rc"]
n := next["n"]
highlight(rc, "green")
KeepExplanationAndHighlightings .= rc n
set(rc, n)
if (caller != "SuperDifficult")
MouseClickCell(rc)
loop, parse, % ConnectedCells[rc], -
PencilMark(a_loopfield, n, 0)
AutoPencil()
history()
text := rc " is the only possible cell for " n " in " unit[u] "."
concat := ""
loop, parse, % UnitCells[u], -
if (a_loopfield != rc)
concat .= explanation[a_loopfield n]
if (concat != "")
text := concat chr(8594) " " text
if (SubStr(text, 1, 1) = chr(8594))
text := SubStr(text, 3)
}
else if (next["action"] = "several possible numbers")
{
if (caller = "SuperDifficult")
return
rc := next["rc"]
SeveralPossibleNumbers := next["n"]
MouseClickCell(rc)
ProvedNumbers := ""
loop, parse, SeveralPossibleNumbers
{
set(rc, a_loopfield)
fill("minimum")
if AnySolution
ProvedNumbers .= a_loopfield
history()
gosub back
}
if (StrLen(ProvedNumbers) = 0)
{
highlight(rc, "red")
text := "This cell could be filled with "
loop, parse, SeveralPossibleNumbers
text .= a_loopfield " or "
text := SubStr(text, 1, -4)
text .= ", but there is no solution with any of them."
}
else
{
if (StrLen(ProvedNumbers) = 1)
{
highlight(rc, "green")
KeepExplanationAndHighlightings .= rc ProvedNumbers
set(rc, ProvedNumbers)
loop, parse, % ConnectedCells[rc], -
PencilMark(a_loopfield, ProvedNumbers, 0)
AutoPencil()
history()
}
else
highlight(rc, "blue")
text := "Possible numbers: "
loop, parse, SeveralPossibleNumbers
text .= a_loopfield ", "
text := SubStr(text, 1, -2) ". There is a solution with: "
loop, parse, ProvedNumbers
text .= a_loopfield ", "
text := SubStr(text, 1, -2) "."
}
}
TextElements := []
loop, parse, text, $
TextElements.push(a_loopfield)
;---- omit repetitions ----
TextElements2 := []
for i, element in TextElements
if not InValues(TextElements2, element)
TextElements2.push(element)
CopyArray(TextElements2, "TextElements", 1)
;--------
TextElements := merge(TextElements, "n")
TextElements := merge(TextElements, "rc")
;---- rc -> coord[rc], n -> ColorName[n], "number" -> "color" ----
text := ""
for i, element in TextElements
text .= element "`n"
text := SubStr(text, 1, -1)
text2 := ""
loopfield1 := ""
loopfield2 := ""
delimiter1 := ""
delimiter2 := ""
loop, parse, text
{
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 = "")
text2 .= delimiter2
else if loopfield2 is digit
{
if (StrLen(loopfield2) = 1)
{
if (display = "colors")
{
if (delimiter1 = "" or delimiter1 = "`n" or delimiter1 = "`r" or delimiter1 = " " and loopfield1 = chr(8594))
text2 .= format("{:T}", ColorName[loopfield2]) delimiter2
else if (loopfield1 != "row" and loopfield1 != "column" and loopfield1 != "block")
text2 .= ColorName[loopfield2] delimiter2
else
text2 .= loopfield2 delimiter2
}
else
text2 .= loopfield2 delimiter2
}
else if (StrLen(loopfield2) = 2)
text2 .= coord[loopfield2] delimiter2
}
else
text2 .= loopfield2 delimiter2
loopfield1 := loopfield2
loopfield2 := ""
}
else
loopfield2 .= a_loopfield
}
if (display = "colors")
text2 := StrReplace(text2, "number", "color")
text := text2
;---- highlight cells ----
loop, parse, AllCells, -
if (highlight[a_loopfield] != "green")
if InStr(text, coord[a_loopfield] " <red>")
highlight(a_loopfield, "red")
else if InStr(text, coord[a_loopfield] " <blue>")
highlight(a_loopfield, "blue")
text := StrReplace(text, " <red>")
text := StrReplace(text, " <blue>")
;---- wrap text ----
text2 := ""
LineLenMax := 120
loop, parse, text, `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)
}
text2 .= "`n" line
}
text := SubStr(text2, 2)
;---- tooltip or gui? ----
loop, parse, text, `n`r
LineCount := a_index
if (caller != "SuperDifficult")
{
if (LineCount <= 4)
tooltip, %text%
else
{
gui, explain:-caption +border
gui, explain:color, FFFFE1
gui, explain:font, s12
gui, explain:add, text,, %text%
gui, explain:show
ArrangeWindows("explain")
}
sleep 100
gui, 1:show
MouseClickCell(rm cm)
}
return
ExplainGuiSize:
ExplainWidth := a_guiwidth
ExplainHeight := a_guiheight
return
ExplainGuiEscape:
gui, explain:destroy
loop, parse, AllCells, -
highlight(a_loopfield, "")
return
;---- set all ---------------------------------------------------------------------------
SetAll:
gui, 1:+OwnDialogs
loop, parse, AllCells, -
highlight(a_loopfield, "")
fill("minimum")
if not AnySolution
msgbox, There is no solution!
else if AnySolution
{
history()
sleep 1000
fill("maximum")
if (AllValues(maximum) = AllValues(minimum))
msgbox, There is only one solution!
else
{
history()
msgbox, There is a second solution!
}
}
return
;---- ? ---------------------------------------------------------------------------------
?:
if (GuiHelp = 1)
{
gui, help:show
return
}
gui, help:font, s12
gui, help:add, text,,
(
Set numbers with the left mouse button or the number keys.
Delete numbers with the left mouse button.
Swap numbers with the S key.
Set/delete pencil marks with the right mouse button or with Shift+number key.
Set all possible pencil marks in a cell with the P key.
Delete everything in a cell with the space bar.
Color the just set number/pencil mark or (three seconds later) any number/pencil mark with the R, G, B, D or C key.
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).
Colors can be used to highlight numbers and pencil marks or to improve a Sudoku image.
"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.
"Switch from numbers to colors" in the View menu is something different:
nine colors REPLACE the nine numbers, and the Sudoku must be solved with colors instead of numbers.
)
gui, help:add, text, xs, % "1 = " ColorName[1]
gui, help:add, text, xp+120, % "2 = " ColorName[2]
gui, help:add, text, xp+120, % "3 = " ColorName[3]
gui, help:add, text, xp+120, % "4 = " ColorName[4]
gui, help:add, text, xp+120, % "5 = " ColorName[5]
gui, help:add, text, xs, % "6 = " ColorName[6]
gui, help:add, text, xp+120, % "7 = " ColorName[7]
gui, help:add, text, xp+120, % "8 = " ColorName[8]
gui, help:add, text, xp+120, % "9 = " ColorName[9]
gui, help:add, text, xs y+10,
(
Highlight a cell/delete the highlighting with Shift/Ctrl/Alt+left click.
Highlight all cells with the same number/delete the highlightings with Shift/Ctrl/Alt+right click on one of the cells.
The arrow keys also move the mouse cursor.
The Esc key deletes the tooltip and all highlightings and cancels "Super difficult".
See the menus for more hotkeys.
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 highlighted cells 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"!
)
gui, help:show,, ?
GuiHelp := 1
ArrangeWindows("help")
return
HelpGuiSize:
HelpWidth := a_guiwidth
HelpHeight := a_guiheight+30 ; +30 for the title bar
return
HelpGuiClose:
HelpGuiEscape:
gui, help:destroy
GuiHelp := 0
return
;========================================================================================
; Other hotkeys for the main GUI
;========================================================================================
~Esc:: ; cancel
tooltip
gui, explain:destroy
loop, parse, AllCells, -
highlight(a_loopfield, "")
swap1 := ""
CancelSuperDifficult := 1
return
space:: ; delete everything in a cell
if (MouseGetCell() and not InStr(FixedCells, rm cm))
{
set(rm cm, 0, "default")
loop, 9
PencilMark(rm cm, a_index, 0, "default")
AutoPencil()
history()
}
return
;---- hotkey letters --------------------------------------------------------------------
r::color("red")
g::color("green")
b::color("blue")
d::color("default")
c::
if (cCustom != "")
color(cCustom)
return
p:: ; set all pencil marks
if MouseGetCell() and not InStr(FixedCells, rm cm)
{
set(rm cm, 0)
loop, 9
{
i := a_index
valid := 1
loop, parse, % ConnectedCells[rm cm], -
if (number[a_loopfield] = i)
{
valid := 0
break
}
if valid
PencilMark(rm cm, i, 1)
}
history()
}
return
s:: ; swap numbers
if MouseGetCell() and not InStr(FixedCells, rm cm)
{
if (number[rm cm] != 0 and swap1 = "")
{
swap1 := number[rm cm]
tooltip, % "Swap " item(swap1) " and ..."
}
else if (number[rm cm] != 0 and number[rm cm] = swap1)
{
swap1 := ""
tooltip
}
else if (number[rm cm] != 0 and swap1 != "" and number[rm cm] != swap1)
{
swap2 := number[rm cm]
loop, parse, AllCells, -
{
if (number[a_loopfield] = swap1)
set(a_loopfield, swap2)
else if (number[a_loopfield] = swap2)
set(a_loopfield, swap1)
}
history()
swap1 := ""
swap2 := ""
tooltip
}
}
return
;---- set numbers -----------------------------------------------------------------------
1::
2::
3::
4::
5::
6::
7::
8::
9::
numpad1::
numpad2::
numpad3::
numpad4::
numpad5::
numpad6::
numpad7::
numpad8::
numpad9::
if (NextInputWait != 1 and MouseGetCell())
{
NextInputWait := 1
n := SubStr(a_thislabel, 0)
if not InStr(FixedCells, rm cm)
{
valid := 1
loop, parse, % ConnectedCells[rm cm], -
if (number[a_loopfield] = n)
{
valid := 0
break
}
if valid
{
set(rm cm, n, "default")
loop, parse, % ConnectedCells[rm cm], -
PencilMark(a_loopfield, n, 0)
AutoPencil()
LastAction := {action: "number", cell: rm cm, n: n, c: "", time: A_TickCount}
history()
}
else
{
set(rm cm, n)
history()
sleep 200
gosub back
history.pop()
}
}
NextInputWait := 0
}
return
;---- set/delete pencil marks -----------------------------------------------------------
+1:: ; set/delete a single pencil mark
+2::
+3::
+4::
+5::
+6::
+7::
+8::
+9::
+numpad1::
+numpad2::
+numpad3::
+numpad4::
+numpad5::
+numpad6::
+numpad7::
+numpad8::
+numpad9::
if (NextInputWait != 1 and MouseGetCell())
{
NextInputWait := 1
n := SubStr(a_thislabel, 0)
if (number[rm cm] = 0)
{
if (PencilMark[rm cm n] = 0)
{
valid := 1
loop, parse, % ConnectedCells[rm cm], -
if (number[a_loopfield] = n)
{
valid := 0
break
}
if valid
{
PencilMark(rm cm, n, 1)
LastAction := {action: "PencilMark", cell: rm cm, n: n, c: "", time: A_TickCount}
history()
}
else
{
PencilMark(rm cm, n, 1)
sleep 200
PencilMark(rm cm, n, 0)
}
}
else
{
PencilMark(rm cm, n, 0, "default")
history()
}
}
NextInputWait := 0
}
return
;---- move the mouse cursor -------------------------------------------------------------
~left::
~right::
~up::
~down::
key := LTrim(a_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<=wBorder ? 1 : 9
if (rm = "")
rm := ym<=hTitleMenu ? 1 : 9
}
MouseClickCell(rm cm)
return
;========================================================================================
; Mouse buttons
;========================================================================================
~lbutton::
if MouseGetCell() and not InStr(FixedCells, rm cm)
{
menu, LeftMouseMenu, deleteall
loop, 9
{
n := a_index
valid := 1
loop, parse, % ConnectedCells[rm cm], -
if (number[a_loopfield] = n)
{
valid := 0
break
}
if valid
{
if (display = "colors")
item := ColorName[n]
else
item := n
menu, LeftMouseMenu, add, %item%, SetDeleteNumber
if (number[rm cm] = n)
menu, LeftMouseMenu, check, %item%
}
}
menu, LeftMouseMenu, show
}
return
SetDeleteNumber:
if (display = "numbers")
n := a_thismenuitem
else
n := ColorNumber[a_thismenuitem]
if (number[rm cm] != n)
{
set(rm cm, n)
loop, parse, % ConnectedCells[rm cm], -
PencilMark(a_loopfield, n, 0)
AutoPencil()
LastAction := {action: "number", cell: rm cm, n: n, c: "", time: A_TickCount}
}
else
{
set(rm cm, 0, "default")
AutoPencil()
}
history()
return
;----------------------------------------------------------------------------------------
~rbutton::
if (MouseGetCell() and number[rm cm] = 0)
{
menu, RightMouseMenu, deleteall
loop, 9
{
n := a_index
valid := 1
loop, parse, % ConnectedCells[rm cm], -
if (number[a_loopfield] = n)
{
valid := 0
break
}
if valid
{
if (display = "colors")
item := "pencil mark " ColorName[a_index]
else
item := "pencil mark " a_index
menu, RightMouseMenu, add, %item%, SetDeletePencilMark
if (PencilMark[rm cm a_index] = 1)
menu, RightMouseMenu, check, %item%
}
}
menu, RightMouseMenu, show
}
return
SetDeletePencilMark:
if (display = "numbers")
n := SubStr(a_thismenuitem, 13)
else
n := ColorNumber[SubStr(a_thismenuitem, 13)]
if (PencilMark[rm cm n] = 0)
{
PencilMark(rm cm, n, 1)
LastAction := {action: "PencilMark", cell: rm cm, n: n, c: "", time: A_TickCount}
}
else
PencilMark(rm cm, n, 0, "default")
history()
return
;----------------------------------------------------------------------------------------
+lbutton::UserHighlight("red")
^lbutton::UserHighlight("green")
!lbutton::UserHighlight("blue")
+rbutton::UserHighlightAllOfOne("red")
^rbutton::UserHighlightAllOfOne("green")
!rbutton::UserHighlightAllOfOne("blue")
UserHighlight(color)
{
global
if MouseGetCell()
{
if (highlight[rm cm] != color)
highlight(rm cm, color)
else
highlight(rm cm, "")
}
}
UserHighlightAllOfOne(color)
{
global
if (MouseGetCell() and number[rm cm] != 0)
{
AllHighlighted := 1
loop, parse, AllCells, -
if (number[a_loopfield] = number[rm cm] and highlight[a_loopfield] != color)
AllHighlighted := 0
if not AllHighlighted
{
loop, parse, AllCells, -
if (number[a_loopfield] = number[rm cm])
highlight(a_loopfield, color)
else if (highlight[a_loopfield] = color)
highlight(a_loopfield, "")
}
else
{
loop, parse, AllCells, -
if (highlight[a_loopfield] = color)
highlight(a_loopfield, "")
}
}
}
;========================================================================================
; Main solving subroutines
;========================================================================================
fill(FillWith)
{
local rc, n
%FillWith% := {}
bifurcation := []
if (FillWith = "minimum")
{
reset := {}
loop, parse, AllCells, -
reset[a_loopfield] := number[a_loopfield]
}
else if (FillWith = "maximum")
{
loop, parse, AllCells, -
set(a_loopfield, reset[a_loopfield])
}
loop
{
GetPossibleNumbers()
if (next["action"] = "solved")
{
loop, parse, AllCells, -
%FillWith%[a_loopfield] := number[a_loopfield]
AnySolution := 1
return
}
if (next["action"] = "no possible number" or next["action"] = "no possible cell")
{
if (bifurcation.length() = 0)
{
AnySolution := 0
return
}
LastBifurcation := bifurcation.pop()
loop, parse, AllCells, -
set(a_loopfield, SubStr(LastBifurcation["string"], a_index, 1))
rc := LastBifurcation["rc"]
if (FillWith = "minimum")
n := SubStr(LastBifurcation["PossibleNumbers"], 1, 1)
else if (FillWith = "maximum")
n := SubStr(LastBifurcation["PossibleNumbers"], 0)
else if (FillWith = "random")
{
n_random := ""
loop, parse, % LastBifurcation["PossibleNumbers"]
n_random .= a_loopfield "-"
sort, n_random, random d-
n := SubStr(n_random, 1, 1)
}
set(rc, n)
if (StrReplace(LastBifurcation["PossibleNumbers"], n) != "")
bifurcation.push({string: LastBifurcation["string"], rc: LastBifurcation["rc"], PossibleNumbers: StrReplace(LastBifurcation["PossibleNumbers"], n)})
}
else if (next["action"] = "one possible number" or next["action"] = "one possible cell")
{
rc := next["rc"]
n := next["n"]
set(rc, n)
}
else if (next["action"] = "several possible numbers")
{
rc := next["rc"]
if (FillWith = "minimum")
n := SubStr(next["n"], 1, 1)
else if (FillWith = "maximum")
n := SubStr(next["n"], 0)
else if (FillWith = "random")
{
n_random := ""
loop, parse, % next["n"]
n_random .= a_loopfield "-"
sort, n_random, random d-
n := SubStr(n_random, 1, 1)
}
set(rc, n)
string := ""
loop, parse, AllCells, -
string .= number[a_loopfield]
bifurcation.push({string: string, rc: rc, PossibleNumbers: StrReplace(next["n"], n)})
}
if (FillWith = "minimum")
{
if (context = "EasyAndSymmetrical" and bifurcation.length() = 0
and number[sym1] != 0
and number[sym2] != 0
and number[sym3] != 0
and number[sym4] != 0
and number[sym5] != 0
and number[sym6] != 0
and number[sym7] != 0
and number[sym8] != 0)
return
if (context = "DifficultButNotSymmetrical" and bifurcation.length() = 0 and number[NextOmit] != 0)
return
}
}
}
;----------------------------------------------------------------------------------------
GetAllPossibleNumbers()
{
local rc
AllPossibleNumbers := {}
loop, parse, AllCells, -
if (number[a_loopfield] = 0)
{
rc := a_loopfield
AllPossibleNumbers[rc] := "123456789"
loop, parse, % ConnectedCells[rc], -
if (number[a_loopfield] != 0)
AllPossibleNumbers[rc] := StrReplace(AllPossibleNumbers[rc], number[a_loopfield])
}
}
;----------------------------------------------------------------------------------------
GetPossibleCells(all := "")
{
local u, n
%all%PossibleCells := {}
loop, 27
{
u := a_index
AlreadySet := ""
loop, parse, % UnitCells[u], -
if (number[a_loopfield] != 0)
AlreadySet .= number[a_loopfield]
loop, 9
if not InStr(AlreadySet, a_index)
{
n := a_index
%all%PossibleCells[u n] := []
loop, parse, % UnitCells[u], -
if (number[a_loopfield] = 0 and InStr(%all%PossibleNumbers[a_loopfield], n))
%all%PossibleCells[u n].push(a_loopfield)
}
}
}
;----------------------------------------------------------------------------------------
GetPossibleNumbers(caller := "")
{
local rc, main_index
; CONTENTS:
; 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 4
; {
; - 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
; }
solved := 1
loop, parse, AllCells, -
if (number[a_loopfield] = 0)
{
solved := 0
break
}
if solved
{
next := {action: "solved"}
return
}
explanation := {} ; explanation[rc n] will be a text string, explaining why n can't be set in cell rc.
GetAllPossibleNumbers()
CopyArray(AllPossibleNumbers, "PossibleNumbers", 1)
if (context = "CreateImage")
loop, parse, PencilImage, -
if (number[a_loopfield] = 0)
loop, 9
if not InStr(PencilledNumbers[a_loopfield], a_index)
PossibleNumbers[a_loopfield] := StrReplace(PossibleNumbers[a_loopfield], a_index)
loop, parse, AllCells, -
if (number[a_loopfield] = 0)
{
if (StrLen(PossibleNumbers[a_loopfield]) = 0)
{
next := {rc: a_loopfield, action: "no possible number"}
return
}
else if (StrLen(PossibleNumbers[a_loopfield]) = 1)
{
next := {rc: a_loopfield, n: PossibleNumbers[a_loopfield], action: "one possible number"}
return
}
}
GetPossibleCells()
for un, cells in PossibleCells
{
u := SubStr(un, 1, -1)
n := SubStr(un, 0)
if (PossibleCells[u n].length() = 0)
{
next := {u: u, n: n, action: "no possible cell"}
return
}
else if (PossibleCells[u n].length() = 1)
{
rc := PossibleCells[u n][1]
next := {u: u, rc: rc, n: n, action: "one possible cell"}
return
}
}
EmptyCells := {}
loop, 27
{
u := a_index
EmptyCells[u] := 0
loop, parse, % UnitCells[u], -
if (number[a_loopfield] = 0)
EmptyCells[u] += 1
}
loop, 9
{
main_index := a_index
if (caller = "SetOne" and main_index = 2)
gosub PleaseWait
CellsWith2PossibleNumbers := ""
loop, parse, AllCells, -
if (number[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 number[a_loopfield] = 0)
for i, n in [n1, n2]
if InStr(PossibleNumbers[a_loopfield], n)
{
rc := a_loopfield
if (caller = "SetOne")
{
explanation[rc n] := n " can't be in " rc " <red> because "
. n1 " and " n2 " are the only possible numbers in "
. cell1 " <blue> and " cell2 " <blue>, so they must be there.$"
concat := ""
loop, parse, % cell1 "-" cell2, -
loop, 9
if (a_index != n1 and a_index != n2) ; no other possible numbers
ExplConcat(concat, explanation[a_loopfield a_index])
if (concat != "")
explanation[rc n] := concat explanation[rc n]
}
if (eliminate(rc, n) = "one left")
return
}
break
}
}
}
;---- two numbers with the same two possible cells ----
for un1, cells1 in PossibleCells
{
u1 := SubStr(un1, 1, -1)
n1 := SubStr(un1, 0)
if (PossibleCells[u1 n1].length() = 2 and EmptyCells[u1] > 2)
for un2, cells2 in PossibleCells
{
u2 := SubStr(un2, 1, -1)
n2 := SubStr(un2, 0)
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 i, cell in PossibleCells[u1 n1]
loop, parse, % PossibleNumbers[cell]
if (a_loopfield != n1 and a_loopfield != n2)
{
n3 := a_loopfield
if (caller = "SetOne")
{
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 " <blue> and " rc2 " <blue>, 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)
ExplConcat(concat, explanation[a_loopfield n1])
loop, parse, % UnitCells[u2], -
if (a_loopfield != rc1 and a_loopfield != rc2)
ExplConcat(concat, explanation[a_loopfield n2])
if (concat != "")
explanation[cell n3] := concat explanation[cell n3]
}
if (eliminate(cell, n3) = "one left")
return
}
break
}
}
}
;---- three cells with together three possible numbers ----
if (caller = "SetOne")
loop, 27
{
u := a_index
loop, parse, % UnitCells[u], -
if (number[a_loopfield] = 0 and StrLen(PossibleNumbers[a_loopfield]) <= 3)
{
cell1 := a_loopfield
i := a_index
loop, parse, % UnitCells[u], -
if (a_index > i and number[a_loopfield] = 0 and PossibleNumbersLen(cell1, a_loopfield) <= 3)
{
cell2 := a_loopfield
i := a_index
loop, parse, % UnitCells[u], -
if (a_index > i and number[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 number[a_loopfield] = 0)
for i, n in [n1, n2, n3]
if InStr(PossibleNumbers[a_loopfield], n)
{
rc := a_loopfield
if (caller = "SetOne")
{
explanation[rc n] := n " can't be in " rc " <red> because "
. n1 " and " n2 " and " n3 " are the only possible numbers in "
. cell1 " <blue> and " cell2 " <blue> and " cell3 " <blue>"
. ", 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
ExplConcat(concat, explanation[a_loopfield a_index])
if (concat != "")
explanation[rc n] := concat explanation[rc n]
}
if (eliminate(rc, n) = "one left")
return
}
}
}
}
}
;---- three numbers with together three possible cells ----
if (caller = "SetOne" and main_index > 1)
for un, cells1 in PossibleCells
{
u := SubStr(un, 1, -1)
n1 := SubStr(un, 0)
if (cells1.length() <= 3 and EmptyCells[u] > 3)
loop, 9
{
n2 := a_index
if (n2 != n1 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[u n3].length() = 2 or PossibleCells[u n3].length() = 3))
{
cells3 := ArrayAdd(cells2, PossibleCells[u n3])
if (cells3.length() = 3)
for i, cell in cells3
loop, parse, % PossibleNumbers[cell]
if (a_loopfield != n1 and a_loopfield != n2 and a_loopfield != n3)
{
n4 := a_loopfield
if (caller = "SetOne")
{
CellsString := ""
for i2, cell2 in cells3
CellsString .= cell2 "-"
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 " <blue> and " rc2
. " <blue> and " rc3 " <blue>. 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 number[a_loopfield] = 0) ; no other possible cells
loop, 3
ExplConcat(concat, explanation[a_loopfield n%a_index%])
if (concat != "")
explanation[cell n4] := concat explanation[cell n4]
}
if (eliminate(cell, n4) = "one left")
return
}
}
}
}
}
}
;---- one number, two or three possible cells, all in the same row or column AND block ----
if (caller = "SetOne")
for un, cells in PossibleCells
{
u := SubStr(un, 1, -1)
n := SubStr(un, 0)
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 number[a_loopfield] = 0 and InStr(PossibleNumbers[a_loopfield], n))
{
rc := a_loopfield
if (caller = "SetOne")
{
text := n " can't be in " rc " <red> because in " unit[u] " it must be in "
loop, parse, % UnitCells[u], -
if (InStr(UnitCells[u2], a_loopfield) and InStr(AllPossibleNumbers[a_loopfield], n))
text .= a_loopfield " <blue> or "
text := SubStr(text, 1, -4)
explanation[rc n] := text ".$"
concat := ""
loop, parse, % UnitCells[u], -
if not InStr(UnitCells[u2], a_loopfield)
ExplConcat(concat, explanation[a_loopfield n])
if (concat != "")
explanation[rc n] := concat explanation[rc n]
}
if (eliminate(rc, n) = "one left")
return
}
break
}
}
;---- X-wing ----
for un, cells in PossibleCells
{
u := SubStr(un, 1, -1)
n := SubStr(un, 0)
if (PossibleCells[u n].length() = 2)
{
A := PossibleCells[u n][1]
B := PossibleCells[u n][2]
loop, % u-1
{
u2 := a_index
C := PossibleCells[u2 n][1]
D := PossibleCells[u2 n][2]
if (PossibleCells[u2 n].length() = 2 and A != C and A != D and B != C and B != D)
{
for i, 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 number[a_loopfield] = 0
and InStr(PossibleNumbers[a_loopfield], n))
{
rc := a_loopfield
if (caller = "SetOne")
{
explanation[rc n] := n
. " can't be in " rc " <red> because of the ""X-wing"" " u2Cell1 " - " u2Cell2 "/" uCell1 " - " uCell2 ":"
. "`n- If in " unit[u2] " " n " is in " u2Cell1 " <blue>, then in " unit[u] " it must be in " uCell2 " <blue>."
. "`n- If in " unit[u] " " n " is in " uCell1 " <blue>, then in " unit[u2] " it must be in " u2Cell2 " <blue>."
. "`nEither 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])
ExplConcat(concat, explanation[a_loopfield n])
loop, parse, % UnitCells[u2], -
if (a_loopfield != PossibleCells[u2 n][1] and a_loopfield != PossibleCells[u2 n][2])
ExplConcat(concat, explanation[a_loopfield n])
if (concat != "")
explanation[rc n] := concat explanation[rc n]
}
if (eliminate(rc, n) = "one left")
return
}
}
}
}
}
}
}
;---- Empty rectangle ----
if (caller = "SetOne")
for un, cells in PossibleCells
{
u := SubStr(un, 1, -1)
n := SubStr(un, 0)
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 i, cell in PossibleCells[u n]
if not InStr(crossroads, cell)
{
AllInCrossroads := 0
break
}
if AllInCrossroads
{
InRoad1 := 0
InRoad2 := 0
for i, 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)
{
loop, 2
{
i_1 := a_index
i_2 := i_1=1 ? 2 : 1
AllEliminations := []
QueuedEliminations := []
IfThen := {}
IfThenElement := []
IfThenBefore := {}
IfThenBeforeElement := []
ExplBefore := {}
ExplBeforeElement := []
CopyArray(PossibleNumbers, "CopyPossibleNumbers", 1)
CopyArray(PossibleCells, "CopyPossibleCells", 2)
loop, parse, % UnitCells[road%i_1%] "-" UnitCells[road%i_2%], -
if (InStr(UnitCells[road%i_1%], a_loopfield) and not InStr(UnitCells[u], a_loopfield)
or InStr(UnitCells[road%i_2%], a_loopfield) and InStr(UnitCells[u], a_loopfield) and not InStr(UnitCells[road%i_1%], a_loopfield))
if number[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[road%i_1%] ", then " rc " <blue> can't be " n
IfThen[rc n] := IfThenElement[1] ".`n"
IfThenBeforeElement[1] := ""
ExplBeforeElement[1] := ""
ContinueEliminating(rc, n, 2)
}
AllEliminations%i_1% := ""
loop, 9
if (AllEliminations[a_index] != "")
AllEliminations%i_1% .= AllEliminations[a_index]
AllEliminations%i_1% := LTrim(AllEliminations%i_1%, "-")
CopyArray(IfThen, "IfThen" i_1, 1)
CopyArray(IfThenBefore, "IfThenBefore" i_1, 1)
CopyArray(ExplBefore, "ExplBefore" i_1, 1)
CopyArray(CopyPossibleNumbers, "PossibleNumbers", 1)
CopyArray(CopyPossibleCells, "PossibleCells", 2)
}
TextCrossRoads := ""
for i, cell in PossibleCells[u n]
TextCrossRoads .= " or " cell " <blue>"
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)
if (caller = "SetOne")
{
explanation[EliminateIn EliminateWhat] := EliminateWhat " can't be in " EliminateIn " <red> 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] .= IfThenBefore%a_index%[EliminateIn EliminateWhat] 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 (number[a_loopfield] = 0 and not InStr(TextCrossRoads, a_loopfield)) ; no other possible cells
ExplConcat(concat, explanation[a_loopfield n])
explanation[EliminateIn EliminateWhat] := concat ExplBefore1[EliminateIn EliminateWhat] ExplBefore2[EliminateIn EliminateWhat] explanation[EliminateIn EliminateWhat]
}
if (eliminate(EliminateIn, EliminateWhat) = "one left")
return
}
}
}
}
}
;---- Y-wing chains ----
if (caller = "SetOne" and main_index > 2)
loop, parse, CellsWith2PossibleNumbers, -
{
rc := a_loopfield
n1 := SubStr(PossibleNumbers[rc], 1, 1)
n2 := SubStr(PossibleNumbers[rc], 2, 1)
loop, 2
{
i_1 := a_index
AllEliminations := []
QueuedEliminations := []
IfThen := {}
IfThenElement := []
IfThenBefore := {}
IfThenBeforeElement := []
ExplBefore := {}
ExplBeforeElement := []
CopyArray(PossibleNumbers, "CopyPossibleNumbers", 1)
CopyArray(PossibleCells, "CopyPossibleCells", 2)
loop, parse, % ConnectedCells[rc], -
if number[a_loopfield] = 0 and InStr(PossibleNumbers[a_loopfield], n%i_1%)
AllEliminations[1] .= "-" a_loopfield n%i_1%
AllEliminations[1] := LTrim(AllEliminations[1], "-")
QueuedEliminations[1] := AllEliminations[1]
loop, parse, % AllEliminations[1], -
{
rc2 := SubStr(a_loopfield, 1, 2)
eliminate(rc2, n%i_1%, 0)
IfThenElement[1] := "If " rc " is " n%i_1% ", then " rc2 " <blue> can't be " n%i_1%
IfThen[rc2 n%i_1%] := IfThenElement[1] ".`n"
IfThenBeforeElement[1] := ""
ExplBeforeElement[1] := ""
ContinueEliminating(rc2, n%i_1%, 2)
}
AllEliminations%i_1% := ""
loop, 9
if (AllEliminations[a_index] != "")
AllEliminations%i_1% .= AllEliminations[a_index]
AllEliminations%i_1% := LTrim(AllEliminations%i_1%, "-")
CopyArray(IfThen, "IfThen" i_1, 1)
CopyArray(IfThenBefore, "IfThenBefore" i_1, 1)
CopyArray(ExplBefore, "ExplBefore" i_1, 1)
CopyArray(CopyPossibleNumbers, "PossibleNumbers", 1)
CopyArray(CopyPossibleCells, "PossibleCells", 2)
}
loop, parse, AllEliminations1, -
if InStr(AllEliminations2, a_loopfield)
{
EliminateWhat := SubStr(a_loopfield, 3, 1)
EliminateIn := SubStr(a_loopfield, 1, 2)
if (caller = "SetOne")
{
explanation[EliminateIn EliminateWhat] := EliminateWhat " can't be in " EliminateIn " <red> "
. "because of the Y-wing chains starting with " n1 " and " n2 " in " rc " <blue>:`n"
loop, 2
explanation[EliminateIn EliminateWhat] .= IfThenBefore%a_index%[EliminateIn EliminateWhat] 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
ExplConcat(concat, explanation[rc a_index])
explanation[EliminateIn EliminateWhat] := concat ExplBefore1[EliminateIn EliminateWhat] ExplBefore2[EliminateIn EliminateWhat] explanation[EliminateIn EliminateWhat]
}
if (eliminate(EliminateIn, EliminateWhat) = "one left")
return
}
}
;---- Swordfish/Jellyfish ----
if (caller = "SetOne")
loop, 9
{
n := a_index
for i, A in ["A", "B"]
{
B := A="A" ? "B" : "A"
line%A% := "row"
pos%A% := 1
plus%A% := 0
letter%A% := [1,2,3,4,5,6,7,8,9]
line%B% := "column"
pos%B% := 2
plus%B% := 9
letter%B% := letter
%lineA% := []
%lineA%[1] := ""
%lineB% := []
%lineB%[1] := ""
if fishnet(2)
return
}
}
if (main_index > 3)
{
change := 0
for rc, n in PossibleNumbers
if (PossibleNumbers[rc] != vorher[rc])
{
change := 1
break
}
if not change
break
}
if (main_index > 2)
CopyArray(PossibleNumbers, "vorher", 1)
}
min := 10
for rc, numbers in AllPossibleNumbers
{
if (StrLen(numbers) = 2)
{
next := {rc: rc, n: numbers, action: "several possible numbers"}
break
}
else if (StrLen(numbers) < min)
{
next := {rc: rc, n: numbers, action: "several possible numbers"}
min := StrLen(numbers)
}
}
}
;----------------------------------------------------------------------------------------
ContinueEliminating(rc, n, index)
{
local i, i2, u, u2, rc2, n1, n2, n_i, un, cell, cells
; ---- one possible number ----
if (StrLen(PossibleNumbers[rc]) = 1)
{
n2 := PossibleNumbers[rc]
NextElements(index)
IfThenElement[index] .= ", " rc " must be " n2
concat1 := ""
concat2 := ""
concat3 := ""
concat4 := ""
loop, 9
if (a_index != n and a_index != n2) ; no other possible numbers
{
ExplConcat(concat1, IfThenBefore[rc a_index])
ExplConcat(concat2, IfThen[rc a_index])
ExplConcat(concat3, ExplBefore[rc a_index])
ExplConcat(concat4, explanation[rc a_index])
}
IfThenBeforeElement[index] .= concat1 concat2
ExplBeforeElement[index] .= concat3 concat4
; ---- 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 number[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 " <blue> 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 i, u in [SubStr(rc, 1, 1), SubStr(rc, 2, 1)+9, block[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 " <blue> is the only possible cell for " n " in " unit[u]
concat1 := ""
concat2 := ""
concat3 := ""
concat4 := ""
loop, parse, % UnitCells[u], -
if (a_loopfield != rc and a_loopfield != rc2 and number[a_loopfield] = 0) ; no other possible cells
{
ExplConcat(concat1, IfThenBefore[a_loopfield n])
ExplConcat(concat2, IfThen[a_loopfield n])
ExplConcat(concat3, ExplBefore[a_loopfield n])
ExplConcat(concat4, explanation[a_loopfield n])
}
IfThenBeforeElement[index] .= concat1 concat2
ExplBeforeElement[index] .= concat3 concat4
; ---- 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 number[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 " <blue> 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 " <blue>"
concat1 := ""
concat2 := ""
concat3 := ""
concat4 := ""
loop, 9
if (a_index != n and a_index != n1 and a_index != n2) ; no other possible numbers
{
ExplConcat(concat1, IfThenBefore[rc a_index])
ExplConcat(concat2, IfThen[rc a_index])
ExplConcat(concat3, ExplBefore[rc a_index])
ExplConcat(concat4, explanation[rc a_index])
}
IfThenBeforeElement[index] .= concat1 concat2
ExplBeforeElement[index] .= concat3 concat4
; ---- same two possible numbers in rc2 ----
loop, parse, % ConnectedCells[rc], -
if (not InStr(IfThenElement[index], a_loopfield) and number[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
{
ExplConcat(concat1, IfThenBefore[rc2 a_index])
ExplConcat(concat2, IfThen[rc2 a_index])
ExplConcat(concat3, ExplBefore[rc2 a_index])
ExplConcat(concat4, explanation[rc2 a_index])
}
IfThenBeforeElement[index] .= concat1 concat2
ExplBeforeElement[index] .= concat3 concat4
; ---- 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 number[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 " <blue>, " a_loopfield " <blue> 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 i, u in [SubStr(rc, 1, 1), SubStr(rc, 2, 1)+9, block[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] " <blue> and " PossibleCells[u n][2] " <blue> are the only possible cells for " n
concat1 := ""
concat2 := ""
concat3 := ""
concat4 := ""
loop, parse, % UnitCells[u], -
if (a_loopfield != rc and a_loopfield != PossibleCells[u n][1] and a_loopfield != PossibleCells[u n][2] and number[a_loopfield] = 0) ; no other possible cells
{
ExplConcat(concat1, IfThenBefore[a_loopfield n])
ExplConcat(concat2, IfThen[a_loopfield n])
ExplConcat(concat3, ExplBefore[a_loopfield n])
ExplConcat(concat4, explanation[a_loopfield n])
}
IfThenBeforeElement[index] .= concat1 concat2
ExplBeforeElement[index] .= concat3 concat4
; ---- same two possible cells for n2 ----
loop, 9
if (a_index != n
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 number[a_loopfield] = 0) ; no other possible cells
{
ExplConcat(concat1, IfThenBefore[a_loopfield n2])
ExplConcat(concat2, IfThen[a_loopfield n2])
ExplConcat(concat3, ExplBefore[a_loopfield n2])
ExplConcat(concat4, explanation[a_loopfield n2])
}
IfThenBeforeElement[index] .= concat1 concat2
ExplBeforeElement[index] .= concat3 concat4
; ---- eliminate other numbers in the two possible cells ----
NewEliminations := ""
for i2, cell in PossibleCells[u n]
{
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 " <blue> 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 i, u in [SubStr(rc, 1, 1), SubStr(rc, 2, 1)+9, block[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] " <blue> and " PossibleCells[u n][2] " <blue>"
if (PossibleCells[u n].length() = 3)
PossibleCellsText .= " and " PossibleCells[u n][3] " <blue>"
NextElements(index)
IfThenElement[index] .= ", that leaves " PossibleCellsText " as possible cells for " n " in " unit[u]
concat1 := ""
concat2 := ""
concat3 := ""
concat4 := ""
loop, parse, % UnitCells[u], -
if (not InValues(PossibleCells[u n], a_loopfield) and number[a_loopfield] = 0) ; no other possible cells
{
ExplConcat(concat1, IfThenBefore[a_loopfield n])
ExplConcat(concat2, IfThen[a_loopfield n])
ExplConcat(concat3, ExplBefore[a_loopfield n])
ExplConcat(concat4, explanation[a_loopfield n])
}
IfThenBeforeElement[index] .= concat1 concat2
ExplBeforeElement[index] .= concat3 concat4
; ---- 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 number[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 " <blue> 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
}
}
}
;========================================================================================
; Other subroutines A-Z
;========================================================================================
AllValues(array) ; for arrays with cells as keys
{
global
string := ""
loop, parse, AllCells, -
string .= array[a_loopfield] "-"
return string
}
;----------------------------------------------------------------------------------------
ArrangeWindows(win2)
{
global
SysGet, screen, MonitorWorkArea
wingetpos, win1left, win1top, win1width, win1height, %WinTitle% ahk_class AutoHotkeyGUI
win1right := win1left+win1width
win1bottom := win1top+win1height
win2width := %win2%width ; Win2GuiSize gets width and height of win2
win2height := %win2%height
if (win1right+10+win2width < screenright)
{
if (win1top+win2height < screenbottom)
gui, %win2%:show, % "x" win1right+10 "y" win1top
else
gui, %win2%:show, % "x" win1right+10 "y" screenbottom-win2height
}
else if (win1bottom+10+win2height < screenbottom)
{
if (win1left+win2width < screenright)
gui, %win2%:show, % "x" win1left "y" win1bottom+10
else
gui, %win2%:show, % "x" screenright-win2width "y" win1bottom+10
}
else if (win1left-10-win2width > 0)
{
if (win1top+win2height < screenbottom)
gui, %win2%:show, % "x" win1left-10-win2width "y" win1top
else
gui, %win2%:show, % "x" win1left-10-win2width "y" screenbottom-win2height
}
else if (win1top-10-win2height > 0)
{
if (win1left+win2width < screenright)
gui, %win2%:show, % "x" win1left "y" win1top-10-win2height
else
gui, %win2%:show, % "x" screenright-win2width "y" win1top-10-win2height
}
else if (win1width+10+win2width < screenright)
{
gui, 1:show, % "x" screenright-(win1width+10+win2width)
if (win1top+win2height < screenbottom)
gui, %win2%:show, % "x" screenright-win2width "y" win1top
else
gui, %win2%:show, % "x" screenright-win2width "y" screenbottom-win2height
}
else if (win1height+10+win2height < screenbottom)
{
gui, 1:show, % "y" screenbottom-(win1height+10+win2height)
if (win1left+win2width < screenright)
gui, %win2%:show, % "x" win1left "y" screenbottom-win2height
else
gui, %win2%:show, % "x" screenright-win2width "y" screenbottom-win2height
}
else
{
ZoomFitWidth := zoom*(screenright-win2width-10-(win1width-9.4*wWhiteSquare))/(9.4*wWhiteSquare)
ZoomFitHeight := zoom*(screenbottom-win2height-10-(win1height-9.4*wWhiteSquare))/(9.4*wWhiteSquare)
; 9.4*wWhiteSquare = 9*wWhiteSquare+wCaption, win1width-9.4*wWhiteSquare = grid lines+borders, win1height-9.4*wWhiteSquare = title bar+menu bar+grid lines+borders
if (ZoomFitWidth < 0.48 and ZoomFitHeight < 0.48)
{
gui, 1:hide
SizeAndPosition(0.5-zoom)
gui, 1:show, y0 w%wGui% h%wGui%
gui, %win2%:show, % "y" screenbottom-win2height
}
else if (ZoomFitWidth > ZoomFitHeight)
{
gui, 1:hide
SizeAndPosition(ZoomFitWidth-zoom)
gui, 1:show, x0 w%wGui% h%wGui%
if (win1top+win2height < screenbottom)
gui, %win2%:show, % "x" wGui+10 "y" win1top
else
gui, %win2%:show, % "x" wGui+10 "y" screenbottom-win2height
}
else
{
gui, 1:hide
SizeAndPosition(ZoomFitHeight-zoom)
gui, 1:show, y0 w%wGui% h%wGui%
if (win1left+win2width < screenright)
gui, %win2%:show, % "x" win1left "y" hTitleMenu+wGui+10
else
gui, %win2%:show, % "x" screenright-win2width "y" hTitleMenu+wGui+10
}
}
}
;----------------------------------------------------------------------------------------
ArrayAdd(array1, array2)
{
local i, element
array1_2 := []
loop, 2
for i, 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"
}
;----------------------------------------------------------------------------------------
AutoPencil()
{
local rc, u, n, un, cells
if not AutoPencil
return
GetAllPossibleNumbers()
GetPossibleCells("All")
;---- reset PencilMark and cPencilMark ----
loop, parse, AllCells, -
if (number[a_loopfield] = 0)
loop, 9
{
PencilMark[a_loopfield a_index] := 0
cPencilMark[a_loopfield a_index] := "default"
}
;---- get singles/pairs and set cPencilMark ----
SinglesOrPairs := {}
for un, cells in AllPossibleCells
if (cells.length() = 1)
{
u := SubStr(un, 1, -1)
n := SubStr(un, 0)
if not InStr(SinglesOrPairs[cells[1]], n) ; Red overwrites cDefault, green doesn't overwrite red, blue doesn't overwrite green.
{
SinglesOrPairs[cells[1]] .= n
cPencilMark[cells[1] n] := AutoColor(u)
}
}
for rc, n in AllPossibleNumbers
if (StrLen(n) = 1)
if not InStr(SinglesOrPairs[rc], n)
SinglesOrPairs[rc] .= n
if (SinglesOrPairs.length() = 0)
{
for un, cells in AllPossibleCells
if (cells.length() = 2)
{
u := SubStr(un, 1, -1)
n := SubStr(un, 0)
loop, 2
if not InStr(SinglesOrPairs[cells[a_index]], n)
{
SinglesOrPairs[cells[a_index]] .= n
cPencilMark[cells[a_index] n] := AutoColor(u)
}
}
for rc, n in AllPossibleNumbers
if (StrLen(n) = 2)
loop, parse, n
if not InStr(SinglesOrPairs[rc], a_loopfield)
SinglesOrPairs[rc] .= a_loopfield
}
;---- set PencilMark ----
for rc, n in %AutoPencil% ; AutoPencil = "AllPossibleNumbers" or "SinglesOrPairs"
loop, parse, n
PencilMark[rc a_loopfield] := 1
;---- apply PencilMark and cPencilMark ----
loop, parse, AllCells, -
if (number[a_loopfield] = 0)
loop, 9
PencilMark(a_loopfield, a_index, PencilMark[a_loopfield a_index], cPencilMark[a_loopfield a_index])
}
;----------------------------------------------------------------------------------------
background(color)
{
global
color := hex(color)
if (cBackground != color)
{
cBackground := color
cBackgroundFor%display% := color
gui, 1:color, %cBackground%
color := SubStr(color, 3) ; omit "0x"
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 := "black"
cFixed := "0x007000"
}
else
{
cDefault := "0xDFDFDF"
cFixed := "0xFFE070"
}
captions(cDefault)
}
}
;----------------------------------------------------------------------------------------
captions(color)
{
global
loop, parse, caption123, -
font("caption123" a_loopfield, color, sCaption, "Ubuntu", "Arial")
loop, parse, captionABC, -
font("captionABC" a_loopfield, color, sCaption, "Ubuntu", "Arial")
}
;----------------------------------------------------------------------------------------
color(c)
{
local i, p
MouseGetCell()
if (LastAction["action"] = "number"
and A_TickCount - LastAction["time"] < 3000)
{
set(LastAction["cell"], LastAction["n"], c)
history()
}
else if (LastAction["action"] = "PencilMark"
and A_TickCount - LastAction["time"] < 3000)
{
PencilMark(LastAction["cell"], LastAction["n"], 1, c)
history()
}
else if (LastAction["action"] = "ColorPencilMark"
and LastAction["c"] = c
and A_TickCount - LastAction["time"] < 1000)
{
OtherPencilMarks := []
loop, 9
if (PencilMark[LastAction["cell"] a_index] = 1 and cPencilMark[LastAction["cell"] a_index] != c)
OtherPencilMarks.push(a_index)
for i, p in OtherPencilMarks
if (p > LastAction["n"])
{
KeepLastAction := 1
gosub back
PencilMark(LastAction["cell"], p, 1, c)
KeepLastAction := 0
LastAction["n"] := p
LastAction["time"] := A_TickCount
history()
break
}
else if (i = OtherPencilMarks.length())
{
KeepLastAction := 1
gosub back
PencilMark(LastAction["cell"], OtherPencilMarks[1], 1, c)
KeepLastAction := 0
LastAction["n"] := OtherPencilMarks[1]
LastAction["time"] := A_TickCount
history()
}
}
else if (number[rm cm] != 0)
{
set(rm cm, number[rm cm], c)
history()
}
else loop, 9
if (PencilMark[rm cm a_index] = 1 and cPencilMark[rm cm a_index] != c)
{
PencilMark(rm cm, a_index, 1, c)
LastAction := {action: "ColorPencilMark", cell: rm cm, n: a_index, c: c, time: A_TickCount}
history()
break
}
}
;----------------------------------------------------------------------------------------
CopyArray(array, CopyName, dimension)
{
local key, index, element1, element2, array2
%CopyName% := {}
for key, element1 in array
{
if (dimension = 1)
%CopyName%[key] := element1
else if (dimension = 2)
{
array2 := []
for index, element2 in element1
array2.push(element2)
%CopyName%[key] := array2
}
}
}
;----------------------------------------------------------------------------------------
CreateImageSave:
if not FileExist(a_desktop "\Sudoku\Create image")
FileCreateDir, %a_desktop%\Sudoku\Create image
if FileExist(a_desktop "\Sudoku\Create image\PencilImage.txt")
filedelete, %a_desktop%\Sudoku\Create image\PencilImage.txt
fileappend, % history[HistoryIndex], %a_desktop%\Sudoku\Create image\PencilImage.txt
AllImagesString := ""
for i, image in AllImages
AllImagesString .= image "-"
AllImagesString := RTrim(AllImagesString, "-")
if FileExist(a_desktop "\Sudoku\Create image\AllImages.txt")
filedelete, %a_desktop%\Sudoku\Create image\AllImages.txt
fileappend, %AllImagesString%, %a_desktop%\Sudoku\Create image\AllImages.txt
loop, 60
if (a_index >= 4)
{
SortedImagesString := ""
for i, image in SortedImages[a_index]
SortedImagesString .= image "-"
SortedImagesString := RTrim(SortedImagesString, "-")
if FileExist(a_desktop "\Sudoku\Create image\SortedImages" a_index ".txt")
filedelete, %a_desktop%\Sudoku\Create image\SortedImages%a_index%.txt
fileappend, %SortedImagesString%, %a_desktop%\Sudoku\Create image\SortedImages%a_index%.txt
}
return
;----------------------------------------------------------------------------------------
eliminate(rc, n, checkout := 1)
{
local i1, i2, u, cell
PossibleNumbers[rc] := StrReplace(PossibleNumbers[rc], n)
if (checkout and StrLen(PossibleNumbers[rc]) = 1)
{
next := {rc: rc, n: PossibleNumbers[rc], action: "one possible number"}
return "one left"
}
for i1, u in [SubStr(rc, 1, 1), SubStr(rc, 2, 1)+9, block[rc]]
for i2, cell in PossibleCells[u n]
if (cell = rc)
{
PossibleCells[u n].RemoveAt(i2)
if (checkout and PossibleCells[u n].length() = 1)
{
next := {u: u, rc: PossibleCells[u n][1], n: n, action: "one possible cell"}
return "one left"
}
break
}
}
;----------------------------------------------------------------------------------------
ExplConcat(byref concat, expl)
{
if (expl != "" and not InStr(concat, expl))
concat .= expl
}
;----------------------------------------------------------------------------------------
fishnet(index)
{
local i, rc
loop, 9
{
%lineA%[index] := %lineA%[index-1]
%lineB%[index] := %lineB%[index-1]
if ((index = 2 or a_index > SubStr(%lineA%[index], 0)) and PossibleCells[a_index+plusA n].length() >= 2)
{
for i, rc in PossibleCells[a_index+plusA n]
if not InStr(%lineB%[index], SubStr(rc, posB, 1))
%lineB%[index] .= SubStr(rc, posB, 1)
if (StrLen(%lineB%[index]) <= 4)
{
%lineA%[index] .= a_index
if (index = 4 and StrLen(%lineB%[index]) = 3 or index = 5 and StrLen(%lineB%[index]) = 4)
{
loop, parse, % %lineB%[index]
loop, parse, % UnitCells[a_loopfield+plusB], -
if (not InStr(%lineA%[index], SubStr(a_loopfield, posA, 1)) and number[a_loopfield] = 0 and InStr(PossibleNumbers[a_loopfield], n))
{
rc := a_loopfield
if (index = 4)
fish := " ""Swordfish"" "
else if (index = 5)
fish := " ""Jellyfish"" "
Text%lineA% := ""
loop, parse, % %lineA%[index]
Text%lineA% .= letterA[a_loopfield] "-"
Text%lineA% := RTrim(Text%lineA%, "-")
Text%lineB% := ""
loop, parse, % %lineB%[index]
Text%lineB% .= letterB[a_loopfield] "-"
Text%lineB% := RTrim(Text%lineB%, "-")
sort, Text%lineB%, d-
TextPossibleCells := ""
loop, % index-1
loop, parse, % UnitCells[SubStr(%lineA%[index], a_index, 1)+plusA], -
if (number[a_loopfield] = 0 and InStr(%lineB%[index], SubStr(a_loopfield, posB, 1)) and InStr(AllPossibleNumbers[a_loopfield], n))
TextPossibleCells .= a_loopfield " <blue>, "
TextPossibleCells := RTrim(TextPossibleCells, ", ")
explanation[rc n] := n " can't be in " rc " <red> because of the" fish "in " lineA " " Text%lineA% "/" lineB " " Text%lineB% ":`n"
. "In " lineA " " Text%lineA% ", " n " can be in " TextPossibleCells ". That's all in " lineB " " Text%lineB% ", and that's one " n " for each " lineB ", "
. "so there can't be any more " n "s in these " lineB "s.$"
concat := ""
loop, % index-1
loop, parse, % UnitCells[SubStr(%lineA%[index], a_index, 1)+plusA], -
if not InStr(%lineB%[index], SubStr(a_loopfield, posB, 1)) ; no other lineB
ExplConcat(concat, explanation[a_loopfield n])
if (concat != "")
explanation[rc n] := concat explanation[rc n]
if (eliminate(rc, n) = "one left")
return 1
}
}
else if (index < 4 or main_index > 1 and index < 5)
if fishnet(index+1)
return 1
}
}
}
}
;----------------------------------------------------------------------------------------
font(TextControl, color, size, font1, font2 := "", weight := 400)
{
global
color := hex(color)
if (cTextControl[TextControl] != color
or sTextControl[TextControl] != size
or wTextControl[TextControl] != weight
or fTextControl[TextControl] != font1
or HighlightOnTop)
{
gui, 1:font, w%weight% c%color% s%size%, %font1%
if (font2 != "")
gui, 1:font,, %font2%
guicontrol, 1:font, %TextControl%
guicontrol, 1:movedraw, %TextControl%
cTextControl[TextControl] := color
sTextControl[TextControl] := size
wTextControl[TextControl] := weight
fTextControl[TextControl] := font1
}
}
;----------------------------------------------------------------------------------------
GuiToString(caller := "")
{
global
string := display "/" cBackground "/"
loop, parse, AllCells, -
if (number[a_loopfield] != 0)
{
string .= a_loopfield number[a_loopfield]
if (display = "numbers" and cNumber[a_loopfield] != "default")
string .= cNumber[a_loopfield]
string .= ","
}
string := StrReplace(string, "xyz")
string := StrReplace(string, "0x")
string := RTrim(string, ",")
string .= "/"
loop, parse, AllCells, -
if (number[a_loopfield] = 0)
loop, 9
if (PencilMark[a_loopfield a_index] = 1)
{
string .= a_loopfield a_index
if (display = "numbers" and cPencilMark[a_loopfield a_index] != "default")
string .= cPencilMark[a_loopfield a_index]
string .= ","
}
string := RTrim(string, ",")
if (caller = "history")
string .= "/" FixedCells
return string
}
;----------------------------------------------------------------------------------------
hex(color)
{
global
if InKeys(ColorValue, color)
color := ColorValue[color]
else if (SubStr(color, 1, 3) = "xyz")
color := SubStr(color, 4)
if (SubStr(color, 1, 2) != "0x")
color := "0x" . color
return color
}
;----------------------------------------------------------------------------------------
highlight(rc, color, restore := 0)
{
global
if (highlight[rc] = color and not restore)
return
highlight[rc] := color
if (color = "")
text("WhiteSquare" rc, "")
else
{
font("WhiteSquare" rc, HighlightValue[color], sHighlight, "Webdings")
text("WhiteSquare" rc, "g")
}
HighlightOnTop := 1
if (number[rc] != 0)
set(rc, number[rc])
else loop, 9
if (PencilMark[rc a_index] = 1)
PencilMark(rc, a_index, 1)
HighlightOnTop := 0
}
;----------------------------------------------------------------------------------------
history()
{
global
string := GuiToString("history")
if (string != history[HistoryIndex])
{
if (history.MaxIndex() > HistoryIndex)
loop, % history.MaxIndex()-HistoryIndex
history.pop()
history.push(string)
HistoryIndex += 1
if (history.MaxIndex() > 99)
{
history.RemoveAt(1)
HistoryIndex -= 1
}
return 1
}
}
;----------------------------------------------------------------------------------------
InKeys(array, var)
{
For key, value in array
if (var = key)
return 1
}
;----------------------------------------------------------------------------------------
InValues(array, var)
{
VarString := var . "x"
For index, value in array
{
ValueString := value . "x" ; "x" makes sure that var and value are compared as strings.
if (VarString = ValueString)
return 1
}
}
;----------------------------------------------------------------------------------------
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]
}
;----------------------------------------------------------------------------------------
item(n)
{
global
if (display = "colors")
return ColorName[n]
else
return n
}
;----------------------------------------------------------------------------------------
LeaveImage:
loop, parse, AllCells, -
if not InStr(image, a_loopfield)
set(a_loopfield, 0)
return
;----------------------------------------------------------------------------------------
merge(array, MergeWhat)
{
local rc, n, i1, i2, element1, element2
n := []
rc := []
for i1, element1 in array
{
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 := []
for i1, element1 in array
{
if skip[i1]
continue
n_merge := n[i1]
rc_merge := rc[i1] " <red>"
for i2, element2 in array
if (i2 > i1)
{
if (MergeWhat = "n")
{
string := StrReplace(array[i2], n[i2] " can't be in " rc[i2], n[i1] " can't be in " rc[i2])
if InStr(string, "X-wing")
string := StrReplace(string, n[i2] " is ", n[i1] " is ")
else if InStr(string, "Empty rectangle") or InStr(string, "Y-wing")
string := StrReplace(string, rc[i2] " <blue> can't be " n[i2], rc[i2] " <blue> can't be " n[i1])
if (string = array[i1])
{
skip[i2] := 1
n_merge .= " and " n[i2]
}
}
else if (MergeWhat = "rc")
{
string := StrReplace(array[i2], n[i2] " can't be in " rc[i2], n[i2] " can't be in " rc[i1])
if InStr(string, "X-wing")
string := StrReplace(string, rc[i2] " is ", rc[i1] " is ")
else if InStr(string, "Empty rectangle") or InStr(string, "Y-wing")
string := StrReplace(string, rc[i2] " <blue> can't be " n[i2], rc[i1] " <blue> can't be " n[i2])
if (string = array[i1])
{
skip[i2] := 1
rc_merge .= " and " rc[i2] " <red>"
}
}
}
if (MergeWhat = "n" and InStr(n_merge, " and "))
{
array[i1] := StrReplace(array[i1], n[i1] " can't be in " rc[i1], n_merge " can't be in " rc[i1])
if InStr(array[i1], "X-wing")
{
array[i1]:= StrReplace(array[i1], n[i1] " is ", n_merge " are ")
array[i1]:= StrReplace(array[i1], " it ", " they ")
}
else if InStr(array[i1], "Empty rectangle") or InStr(array[i1], "Y-wing")
array[i1] := StrReplace(array[i1], rc[i1] " <blue> can't be " n[i1], rc[i1] " can't be " n_merge)
}
else if (MergeWhat = "rc" and InStr(rc_merge, " and "))
{
array[i1] := StrReplace(array[i1], n[i1] " can't be in " rc[i1], n[i1] " can't be in " rc_merge)
array[i1] := StrReplace(array[i1], "<red> <red>", "<red>")
if InStr(array[i1], "X-wing")
array[i1]:= StrReplace(array[i1], rc[i1] " is ", rc_merge " are ")
else if InStr(array[i1], "Empty rectangle") or InStr(array[i1], "Y-wing")
array[i1] := StrReplace(array[i1], rc[i1] " <blue> can't be " n[i1], rc_merge " can't be " n[i1])
}
array2.push(array[i1])
}
return array2
}
;----------------------------------------------------------------------------------------
MergeIfThen(MergePos)
{
local 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)
text1 := []
MergeText := []
text2 := []
for i1, element1 in IfThen1
if (i1 > 1)
{
text1[i1] := ""
MergeText[i1] := ""
text2[i1] := ""
loop, parse, element1, `,
{
if (a_index < MergePos)
text1[i1] .= a_loopfield ","
else if (a_index = MergePos)
{
loop, parse, a_loopfield, %a_space%
{
if (MergePos = 2)
{
if (a_index = 2)
text1[i1] .= " " a_loopfield ; then ...
else if (a_index = 3 or a_index = 4)
MergeText[i1] .= " " a_loopfield ; ... rc <blue> ...
else if (a_index > 4)
text2[i1] .= " " a_loopfield ; ... can't be n
}
else
{
if (a_index = 2 or a_index = 3)
MergeText[i1] .= " " a_loopfield ; rc <blue> ...
else if (a_index > 3)
text2[i1] .= " " a_loopfield ; ... can't be n
}
}
}
else if (a_loopfield != "")
text2[i1] .= "," a_loopfield
}
}
IfThen2 := []
skip := []
for i1, element1 in IfThen1
{
if (i1 = 1)
{
IfThen2.push(element1)
continue
}
if skip[i1]
continue
merge := MergeText[i1]
for i2, element2 in IfThen1
if (i2 > i1
and text1[i1] = text1[i2]
and (InStr(text2[i1], RTrim(text2[i2], ".")) or InStr(text2[i2], RTrim(text2[i1], "."))))
{
skip[i2] := 1
if not InStr(merge, MergeText[i2])
merge .= "-" MergeText[i2]
if not InStr(text2[i1], RTrim(text2[i2], "."))
text2[i1] := text2[i2]
}
if InStr(merge, "-")
{
sort, merge, d-
merge := StrReplace(merge, "-", " and")
}
IfThen2.push(text1[i1] merge text2[i1])
}
explanation[EliminateIn EliminateWhat] := ""
for i2, element2 in IfThen2
explanation[EliminateIn EliminateWhat] .= element2 "`n"
}
;----------------------------------------------------------------------------------------
MouseClickCell(rc)
{
global
rm := SubStr(rc, 1, 1)
cm := SubStr(rc, 2, 1)
x := wBorder + pos[cm] + wWhiteSquare/2
y := hTitleMenu + pos[rm] + wWhiteSquare/2
click %x%, %y%, 0
}
;----------------------------------------------------------------------------------------
MouseGetCell()
{
global
mousegetpos, xm, ym
cm := ""
rm := ""
loop, 9
if (xm >= wBorder+pos[a_index] and xm < wBorder+pos[a_index+1])
{
cm := a_index
break
}
loop, 9
if (ym >= hTitleMenu+pos[a_index] and ym < hTitleMenu+pos[a_index+1])
{
rm := a_index
break
}
if (cm != "" and rm != "")
return 1
}
;----------------------------------------------------------------------------------------
NextElements(index)
{
global
IfThenElement[index] := IfThenElement[index-1]
IfThenBeforeElement[index] := IfThenBeforeElement[index-1]
ExplBeforeElement[index] := ExplBeforeElement[index-1]
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.
RevPattern := "" ; in reverse order ...
loop, parse, pattern
RevPattern := a_loopfield RevPattern
FirstPossibleIncrease := ""
loop, parse, RevPattern ; ... to loop higher numbers first
if (a_loopfield < maximum-(a_index-1)) ; For a possible increase, the first digit of RevPattern (= 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 RevPattern
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
}
;----------------------------------------------------------------------------------------
PencilMark(rc, p, value, color := "")
{
global
if not KeepLastAction
LastAction := ""
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, ColorValue[ColorName[p]], sPencilColor, "Webdings")
text("PencilMark" rc p, "=")
}
else
{
if (highlight[rc] != "" and cPencilMark[rc p] = "default")
color := "black"
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, "Ubuntu", "Arial", weight)
text("PencilMark" rc p, p)
}
}
}
;----------------------------------------------------------------------------------------
PleaseWait:
wingetpos, winx, winy, winwidth, winheight, %WinTitle% ahk_class AutoHotkeyGUI
x := winx+winwidth//2-2*wWhiteSquare
y := winy+winheight//2-wWhiteSquare//2
s := sNumber*3//5
gui, 1:+disabled
gui, PleaseWait:+owner1 -caption
gui, PleaseWait:font, s%s%
gui, PleaseWait:add, text,, Please wait ...
gui, PleaseWait:show, x%x% y%y% NoActivate
return
;----------------------------------------------------------------------------------------
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 "-"
PossibleNumbersLen := StrLen(PossibleNumbersString)/2
if (PossibleNumbersLen = 3)
{
sort, PossibleNumbersString, d-
n1 := SubStr(PossibleNumbersString, 1, 1)
n2 := SubStr(PossibleNumbersString, 3, 1)
n3 := SubStr(PossibleNumbersString, 5, 1)
}
return PossibleNumbersLen
}
;----------------------------------------------------------------------------------------
set(rc, n, color := "")
{
global
if not KeepLastAction
LastAction := ""
if (KeepExplanationAndHighlightings != "" and not InStr(KeepExplanationAndHighlightings, rc n))
{
tooltip
gui, explain:destroy
loop, parse, AllCells, -
highlight(a_loopfield, "")
KeepExplanationAndHighlightings := ""
}
number[rc] := n
if (color != "")
cNumber[rc] := color
if (context = "CreateImage" and not InStr(image, rc))
return
if (context = "EasyAndSymmetrical" or context = "DifficultButNotSymmetrical")
return
if (n = 0)
text("number" rc, "")
else
{
loop, 9
PencilMark(rc, a_index, 0)
if (display = "colors")
{
font("number" rc, ColorValue[ColorName[n]], sColor, "Webdings")
text("number" rc, "=")
}
else
{
if (highlight[rc] != "")
color := "black"
else if (cNumber[rc] = "default")
{
if InStr(FixedCells, rc)
color := cFixed
else
color := cDefault
}
else
color := cNumber[rc]
font("number" rc, color, sNumber, "Ubuntu", "Arial")
text("number" rc, n)
}
}
}
;----------------------------------------------------------------------------------------
SizeAndPosition(plus := 0)
{
global
size := {"wWhiteSquare": 80, "sHighlight": 60, "sGreySquare": 64, "sNumber": 50, "sColor": 54
, "wPencilMark": 22, "sPencilNumber": 14, "sPencilColor": 12, "wCaption": 32, "sCaption": 20}
ZoomMax := round(a_screenheight/960, 2) ; 12 squares should not be higher than the screen.
if (plus = 0 and wGui != "" or plus < 0 and zoom < 0.52 or plus > 0 and zoom > ZoomMax-0.02)
return 0
if (zoom+plus < 0.48)
zoom := 0.48
else if (zoom+plus > ZoomMax+0.02)
zoom := ZoomMax+0.02
else
zoom += plus
for var, value in size
%var% := floor(zoom*value)
if (a_screendpi != 96)
{
sHighlight := sHighlight*96//a_screendpi
sGreySquare := sGreySquare*96//a_screendpi
sNumber := sNumber*96//a_screendpi
sColor := sColor*96//a_screendpi
sPencilNumber := sPencilNumber*96//a_screendpi
sPencilColor := sPencilColor*96//a_screendpi
sCaption := sCaption*96//a_screendpi
}
if (zoom < 1)
wLine := 1
else if (zoom < 1.5)
wLine := 2
else if (zoom < 2)
wLine := 3
else
wLine := 4
pos := []
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
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 Set() and PencilMark() to avoid this.
if (number[a_loopfield] != 0)
set(a_loopfield, number[a_loopfield])
else loop, 9
if (PencilMark[a_loopfield a_index] = 1)
PencilMark(a_loopfield, a_index, 1)
if (highlight[a_loopfield] != "")
highlight(a_loopfield, highlight[a_loopfield], 1)
r := SubStr(a_loopfield, 1, 1)
c := SubStr(a_loopfield, 2, 1)
font("GreySquare" a_loopfield, "0x818181", sGreySquare, "Webdings")
guicontrol, 1:movedraw, GreySquare%a_loopfield%, % "x" pos[c] "y" pos[r] "w" pos[c+1]-pos[c] "h" pos[r+1]-pos[r]
; GreySquare is not always an exact square.
guicontrol, 1:movedraw, WhiteSquare%a_loopfield%, % "x" pos[c] "y" pos[r] "w" wWhiteSquare "h" wWhiteSquare
guicontrol, 1:movedraw, number%a_loopfield%, % "x" pos[c] "y" pos[r] "w" wWhiteSquare "h" wWhiteSquare
loop, 9
guicontrol, 1:movedraw, PencilMark%a_loopfield%%a_index%, % "x" pos[c]+xAdd[a_index] "y" pos[r]+yAdd[a_index] "w" wPencilMark "h" wPencilMark
}
loop, parse, caption123, -
guicontrol, 1:movedraw, caption123%a_loopfield%, % "x" pos[10] "y" pos[a_index]+wWhiteSquare*0.3 "w" wCaption "h" wWhiteSquare
loop, parse, captionABC, -
guicontrol, 1:movedraw, captionABC%a_loopfield%, % "x" pos[a_index] "y" pos[10] "w" wWhiteSquare "h" wCaption
captions(cDefault)
return 1
}
;----------------------------------------------------------------------------------------
StringToGui(string)
{
local rc
string1 := ""
loop, parse, AllCells, -
string1 .= a_loopfield number[a_loopfield] "-"
loop, parse, AllCells, -
{
number[a_loopfield] := 0
cNumber[a_loopfield] := "default"
loop, 9
{
PencilMark[a_loopfield a_index] := 0
cPencilMark[a_loopfield a_index] := "default"
}
}
loop, 5
SubStr%a_index% := ""
loop, parse, string, /, `n`r
SubStr%a_index% := a_loopfield
if (display != SubStr1)
{
menu, ViewMenu, rename, 5&, S&witch from %SubStr1% to %display%
display := SubStr1
}
if (cBackground != SubStr2)
background(SubStr2)
loop, parse, SubStr3, `,
{
rc := SubStr(a_loopfield, 1, 2)
number[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 != "")
{
menu, SudokuMenu, rename, 5&, &Unfix
menu, SudokuMenu, add, &Unfix, unfix
}
else if (FixedCells != "" and SubStr5 = "")
{
menu, SudokuMenu, rename, 5&, &Fix
menu, SudokuMenu, add, &Fix, fix
}
FixedCells := SubStr5
loop, parse, AllCells, -
{
set(a_loopfield, number[a_loopfield], cNumber[a_loopfield])
if (number[a_loopfield] = 0)
loop, 9
PencilMark(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 number[a_loopfield])
{
changes += 1
if (changes > 1)
{
tooltip
gui, explain:destroy
loop, parse, AllCells, -
highlight(a_loopfield, "")
break
}
}
}
;----------------------------------------------------------------------------------------
text(TextControl, text)
{
global
if (tTextControl[TextControl] != text or HighlightOnTop)
{
guicontrol, 1:text, %TextControl%, %text%
tTextControl[TextControl] := text
}
}