Sudoku

Post your working scripts, libraries and tools
pekoe
Posts: 6
Joined: 25 Mar 2016, 11:42

Sudoku

25 Mar 2016, 14:13

Generates random Sudokus (easy and symmetrical, or difficult but not symmetrical) and creates Sudoku images.
Solves every Sudoku, finds and explains X-wings, "empty rectangles", Swordfish, Jellyfish and chains.
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

; 2022-11-17
; 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
; Main subroutines for GetPossibleNumbers:
;	chain
;	EmptyRectangle
;	PossiblePush
;	fishnet
; 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 := []
UnitType := []
loop, 27
{
	if (a_index <= 9)
	{
		unit[a_index] := "row " a_index
		UnitType[a_index] := "row"
	}
	else if (a_index <= 18)
	{
		unit[a_index] := "column " letter[a_index-9]
		UnitType[a_index] := "column"
	}
	else
	{
		unit[a_index] := "block " (a_index-18)
		UnitType[a_index] := "block"
	}
}

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
}
UnitCells[19] := intersection(1, 2, 3, 1, 2, 3)	; blocks
UnitCells[20] := intersection(1, 2, 3, 4, 5, 6)
UnitCells[21] := intersection(1, 2, 3, 7, 8, 9)
UnitCells[22] := intersection(4, 5, 6, 1, 2, 3)
UnitCells[23] := intersection(4, 5, 6, 4, 5, 6)
UnitCells[24] := intersection(4, 5, 6, 7, 8, 9)
UnitCells[25] := intersection(7, 8, 9, 1, 2, 3)
UnitCells[26] := intersection(7, 8, 9, 4, 5, 6)
UnitCells[27] := intersection(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] := ""
	RowColumnBlock := UnitCells[r] "-" UnitCells[c+9] "-" UnitCells[block[rc]]
	loop, parse, RowColumnBlock, -
		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 := "0xDDEEFF"

;========================================================================================
;	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; if cPencilMark[cell n] is "A", its value is cAutoPencilled.
; The values of cDefault, cFixed and cAutoPencilled 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, &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, Christmas tree, ChristmasTree
menu, SudokuMenu, add, crown
menu, SudokuMenu, add, sun
menu, SudokuMenu, add, star
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 marks for singles or pairs, PencilMarkSinglesOrPairs
menu, PlayMenu, add, &Remove pencil marks, RemovePencilMarks
menu, PlayMenu, add, &Clear board, ClearBoard

menu, SolveMenu, add, Find one	Ctrl+page down, FindOne
menu, SolveMenu, add, Find &all, FindAll

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 >= 1224 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 (AllSinglePossible = 1)
		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 = 1)
			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 (AllSinglePossible = 1)
		StillOneSolution := 1
	else if (AllValues(minimum) = AllValues(random))
	{
		context := "DifficultButNotSymmetrical"
		fill("maximum")
		context := ""
		if (AllValues(maximum) = AllValues(minimum))
			StillOneSolution := 1
	}
	if (StillOneSolution = 1)
		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

;---- 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%
	gosub UpdateOldFile
	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, 4&, &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, 4&, &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/2480024FF,254006DFF,26900B6FF,3352500FF,37200FFFF,4246E00FF,"
. "452FFB700,48700FFB7,529B700FF,541FFFF00,563FF6E00,58400FF6E,628FF00FF,664FF2500,68500FF25,"
. "737FF00B6,746FF006D,755FF0024,78124FF00,8736DFF00,961B6FF00"
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/147F08000,155F08000,161F08000,231F08000,278F08000,327F08000,346F08000,"
. "369F08000,381F08000,428F08000,484F08000,525F08000,536F08000,573F08000,587F08000,621F08000,"
. "642F08000,657F08000,664F08000,685F08000,733F08000,771F08000,845F08000,853F08000,862F08000"
StringToGui(string)
gosub fix
AutoPencil := 0
history()
return

;---- Christmas tree --------------------------------------------------------------------

ChristmasTree:
string := "colors/DDEEFF/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/DDEEFF/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/FFFFFF/132FFC060,175FFC060,246FF9030,268FF9030,314FF60C0,331FF6060,"
. "357FF6000,373FF6060,396FF60C0,426FF3090,448FF3030,462FF3030,481FF3090,537FF0060,576FF0060,"
. "624FF3090,647FF3030,663FF3030,682FF3090,715FF60C0,734FF6060,758FF6000,772FF6060,799FF60C0,"
. "849FF9030,861FF9030,933FFC060,977FFC060"
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.
;	Numbers that would lead to a repetition (PencilImageString already in ContinuedImages) are eliminated by GetPossibleNumbers().
;	loop
;	{
;		loop PencilImage
;		{
;			Try all pencilled and possible numbers in a_loopfield.
;			If the resulting image is already in ContinuedImages, 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 NewImages.
;		}
;		Sort NewImages according to the number of differences, set the best image, add it to ContinuedImages
;		and continue the second loop.
;		If there are no new images, break the second loop.
;	}
; }

if (GuiCreateImage = 1)
{
	gui, CreateImage:show
	return
}
if (GuiPaintImage = 1)
{
	gui, PaintImage:show
	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
context := ""
return

PaintImageButtonCreateImage:
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
ContinuedImages := []
CancelWait := 0
;---- Create a Sudoku from the image ----
loop
{
	if (a_index = 1)
	{
		fill("random")
		if (AnySolution = 0)
		{
			msgbox, There is no solution.
			context := "CreateImage_PaintImage"
			history()
			gosub back
			return
		}
		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
	{
		StartOver := 1
		fill("minimum")
		StartOver := 0
		if (AnySolution = 0)
		{
			guicontrol, CreateImage:text, CreateImageText, CAN'T CREATE A SUDOKU WITH A UNIQUE SOLUTION.
			context := ""
			return
		}
	}
	gosub LeaveImage
	examined := 0
	DifferencesInContinuedImage := ""
	loop
	{
		main_index := a_index
		PencilledAndPossibleNumbers := PencilledNumbers.clone()
		loop, parse, PencilImage, -
		{
			rc := a_loopfield
			loop, parse, % ConnectedCells[rc], -
				if (number[a_loopfield] != 0)
					PencilledAndPossibleNumbers[rc] := StrReplace(PencilledAndPossibleNumbers[rc], number[a_loopfield])
		}
		NewImages := ""
		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]
			{
				set(PencilImageLoopfield, a_loopfield)
				PencilImageString := ""
				loop, parse, PencilImage, -
					PencilImageString .= number[a_loopfield]
				if (InValues(ContinuedImages, PencilImageString)
				or main_index = 1 and PencilImageIndex > 1 and a_loopfield = StartingValue)
					continue
				examined += 1
				fill("minimum")
				if (AnySolution = 1)
				{
					fill("maximum")
					gosub LeaveImage
					differences := 0
					loop, parse, AllCells, -
						if (maximum[a_loopfield] != minimum[a_loopfield])
							differences += 1
					if (differences = 0)
					{
						SoundBeep, 480, 240
						sleep 80
						SoundBeep, 480, 240
						sleep 80
						SoundBeep, 720, 240
						sleep 80
						SoundBeep, 720, 240
						sleep 80
						SoundBeep, 800, 240
						sleep 80
						SoundBeep, 800, 240
						sleep 80
						SoundBeep, 720, 400
						gui, 1:-disabled
						guicontrol, CreateImage:text, CreateImageText,
(
Loops: %main_index%
Examined images: %examined%

Ready!
You can improve the image by swapping numbers
and by coloring, see ? and the View menu.
)
						gui, CreateImage:add, button, x+20, Save
						highlight(HighlightedLoopfield, "")
						HighlightedLoopfield := ""
						history()
						context := ""
						return
					}
					else
					{
						NewImages .= differences "/" PencilImageString "-"
						guicontrol, CreateImage:text, CreateImageText,
(
Loop %main_index%
Examined images: %examined%
Cells with no unique solution
- in the last examined image: %differences%
- in the best image of the preceding loop: %DifferencesInContinuedImage%

Please wait, this can take some time ...
)
					}
				}
				if CancelWait
					gosub CancelNow
			}
			set(PencilImageLoopfield, StartingValue)
		}
		if (NewImages != "")
		{
			sort, NewImages, n d-
			loop, parse, NewImages, -
			{
				BestImage := a_loopfield
				break
			}
			loop, parse, BestImage, /
				if (a_index = 1)
					differences := a_loopfield
				else
					PencilImageString := a_loopfield
			loop, parse, PencilImage, -
				set(a_loopfield, SubStr(PencilImageString, a_index, 1))
			ContinuedImages.push(PencilImageString)
			DifferencesInContinuedImage := differences
		}
		else
		{
			history()
			gosub back
			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:
CancelWait := 0
gui, 1:-disabled
gui, CreateImage:destroy
GuiCreateImage := 0
if (HighlightedLoopfield != "")
{
	highlight(HighlightedLoopfield, "")
	HighlightedLoopfield := ""
}
if history()
	gosub back
context := ""
exit

CreateImageButtonSave:
gui, CreateImage:destroy
GuiCreateImage := 0
gosub SaveAs
return

;---- larger ----------------------------------------------------------------------------

larger:
+::
NumpadAdd::
MouseGetCell()  ; returns rm and cm
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
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
if SizeAndPosition(1-zoom)
	gui, 1:show, w%wGui% h%wGui%
if (rm != "" and cm != "")
	MouseClickCell(rm cm)
return

;---- mix color -------------------------------------------------------------------------

MixColor:
^c::
if (display = "colors")
	return
if (GuiMixColor = 1)
{
	gui, MixColor:show
	return
}
gui, MixColor:font, s12
gui, MixColor:add, Text,, Mix a color:
mix := cMix!="" ? SubStr(cMix, 4) : ""
split(mix)
gui, MixColor:add, text, xm, Red:
gui, MixColor:add, slider, xp+60 vrInput gslider AltSubmit range0-255, %red%
gui, MixColor:add, text, xm, Green:
gui, MixColor:add, slider, xp+60 vgInput gslider AltSubmit range0-255, %green%
gui, MixColor:add, text, xm, Blue:
gui, MixColor:add, slider, xp+60 vbInput gslider AltSubmit range0-255, %blue%
gui, MixColor:font, s60 c%mix%, 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, %mix%
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:add, button, xm, Set the color as background
gui, MixColor:add, button, xm, Close
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}", rInput*16**4+gInput*16**2+bInput)
cMix := "xyz" . mix
; Without "xyz", cMix 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().
GuiControl, text, MixValue, %mix%
gui, MixColor:font, s60 c%mix%, Webdings
GuiControl, font, MixColor
return

MixColorButtonSetTheColorAsBackground:
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()
}
return

MixColorButtonClose:
MixColorGuiClose:
MixColorGuiEscape:
msgbox, 0x2003,, Do you want to keep the mixed color?
IfMsgBox, Yes
{
	mix := ""
	gui, MixColor:destroy
	GuiMixColor := 0
}
IfMsgBox, No
{
	mix := ""
	cMix := ""
	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, enable, 4&
	menu, ViewMenu, rename, 5&, S&witch from numbers to colors
}
else
{
	display := "colors"
	background(cBackgroundForColors)
	menu, ViewMenu, disable, 4&
	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 := 0
GetAllPossibleNumbers()
for rc, numbers in AllPossibleNumbers
	loop, 9
		if InStr(numbers, a_index)
			PencilMark(rc, a_index, 1)
		else
			PencilMark(rc, a_index, 0)
history()
return

;---- pencil marks for singles or pairs -------------------------------------------------

PencilMarkSinglesOrPairs:
for rcn, x in PencilMark
	if (x = 1)
{
	rc := SubStr(rcn, 1, 2)
	n := SubStr(rcn, 3, 1)
	PencilMark(rc, n, 0)
}
AutoPencil := 1
AutoPencil()
return

AutoPencil()
{
	local rc, n, u
	if (AutoPencil = 0
	or context = "CreateImage_PaintImage"
	or context = "CreateImage")
		return
	GetAllPossibleNumbers()
	PossibleCells := {}
	loop, 27
	{
		u := a_index
		loop, 9
		{
			n := a_index
			PossibleCells[u n] := []
			loop, parse, % UnitCells[u], -
			{
				if (number[a_loopfield] = n)
					break
				else if (number[a_loopfield] = 0 and InStr(AllPossibleNumbers[a_loopfield], n))
					PossibleCells[u n].push(a_loopfield)
			}
		}
	}
	singles := []
	;---- pencil marks for one possible number ----
	for rc, numbers in AllPossibleNumbers
		if (StrLen(numbers) = 1)
	{
		n := numbers
		PencilMark(rc, n, 1, "A")
		singles.push(rc n)
	}
	;---- pencil marks for one possible cell ----
	for un, cells in PossibleCells
		if (cells.length() = 1)
	{
		n := SubStr(un, 0)
		if not InValues(singles, cells[1] n)
		{
			PencilMark(cells[1], n, 1, "default")
			singles.push(cells[1] n)
		}
	}
	if (singles.length() != 0)
	{
		for rcn, x in PencilMark
			if (x = 1 and not InValues(singles, rcn))
		{
			rc := SubStr(rcn, 1, 2)
			n := SubStr(rcn, 3, 1)
			PencilMark(rc, n, 0)
		}
	}
	else
	{
		pairs := []
		;---- pencil marks for two possible numbers ----
		for rc, numbers in AllPossibleNumbers
			if (StrLen(numbers) = 2)
				loop, parse, numbers
		{
			PencilMark(rc, a_loopfield, 1, "A")
			pairs.push(rc a_loopfield)
		}
		;---- pencil marks for two possible cells ----
		for un, cells in PossibleCells
			if (cells.length() = 2)
		{
			n := SubStr(un, 0)
			if not InValues(pairs, cells[1] n)
			{
				PencilMark(cells[1], n, 1, "default")
				pairs.push(cells[1] n)
			}
			if not InValues(pairs, cells[2] n)
			{
				PencilMark(cells[2], n, 1, "default")
				pairs.push(cells[2] n)
			}
		}
	}
	history()
}

;---- remove pencil marks ---------------------------------------------------------------

RemovePencilMarks:
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

;---- find one --------------------------------------------------------------------------

FindOne:
^pgdn::
loop, parse, AllCells, -
	highlight(a_loopfield, "")
context := "FindOne"
GetPossibleNumbers()
gui, 1:-disabled
gui, PleaseWait:destroy
context := ""
text := ""
if (WhatNext["text"] = "no empty cells")
	return
else if (WhatNext["text"] = "no possible number")
{
	rc := WhatNext["rc"]
	highlight(rc, "red")
	MouseClickCell(rc)
	text := "This cell can't be filled.$"
	loop, 9
		ExplanationAdd("text", rc a_index)
}
else if (WhatNext["text"] = "no possible cell")
{
	u := WhatNext["u"]
	n := WhatNext["n"]
	loop, parse, % UnitCells[u], -
		if (number[a_loopfield] = 0)
	{
		highlight(a_loopfield, "red")
		MouseClickCell(a_loopfield)
		sleep 400
	}
	text := item(n) " can't be set anywhere in " unit[u] ".$"
	loop, parse, % UnitCells[u], -
		ExplanationAdd("text", a_loopfield n)
}
else if (WhatNext["text"] = "one possible number")
{
	rc := WhatNext["rc"]
	n := WhatNext["n"]
	set(rc, n)
	highlight(rc, "green")
	MouseClickCell(rc)
	loop, parse, % ConnectedCells[rc], -
		PencilMark(a_loopfield, n, 0)
	AutoPencil()
	history()
	text := item(n) " is the only possible number in this cell.$"
	loop, 9
		if (a_index != n)
			ExplanationAdd("text", rc a_index)
}
else if (WhatNext["text"] = "one possible cell")
{
	u := WhatNext["u"]
	rc := WhatNext["rc"]
	n := WhatNext["n"]
	set(rc, n)
	highlight(rc, "green")
	MouseClickCell(rc)
	loop, parse, % ConnectedCells[rc], -
		PencilMark(a_loopfield, n, 0)
	AutoPencil()
	history()
	text := "This is the only possible cell for " item(n) " in " unit[u] ".$"
	loop, parse, % UnitCells[u], -
		if (a_loopfield != rc)
			ExplanationAdd("text", a_loopfield n)
}
else if (WhatNext["text"] = "several possible numbers")
{
	rc := WhatNext["rc"]
	SeveralPossibleNumbers := WhatNext["n"]
	MouseClickCell(rc)
	ProvedNumbers := ""
	loop, parse, SeveralPossibleNumbers
	{
		set(rc, a_loopfield)
		fill("minimum")
		if (AnySolution = 1)
			ProvedNumbers .= a_loopfield
		history()
		gosub back
	}
	if (StrLen(ProvedNumbers) = 0)
	{
		highlight(rc, "red")
		text := "This cell could be filled with "
		loop, parse, SeveralPossibleNumbers
			text .= item(a_loopfield) " or "
		text := SubStr(text, 1, -4)
		text .= ", but there is no solution with any of them.$"
	}
	else
	{
		if (StrLen(ProvedNumbers) = 1)
		{
			set(rc, ProvedNumbers)
			highlight(rc, "green")
			loop, parse, % ConnectedCells[rc], -
				PencilMark(a_loopfield, ProvedNumbers, 0)
			AutoPencil()
			history()
		}
		else
			highlight(rc, "blue")
		text := "Possible numbers: "
		loop, parse, SeveralPossibleNumbers
			text .= item(a_loopfield) ", "
		text := SubStr(text, 1, -2)
		text .= ". There is a solution with: "
		loop, parse, ProvedNumbers
			text .= item(a_loopfield) ", "
		text := SubStr(text, 1, -2)
		text .= ".$"
	}
}
;---- omit repetitions ----
text2 := ""
loop, parse, text, $
	if (a_loopfield != "" and not InStr(text2, a_loopfield))
		text2 .= a_loopfield "$"
text := text2
;---- merge explanations ----
text2 := ""
loop, parse, text, $
	if (a_loopfield != "")
{
	if (SubStr(a_loopfield, FirstSpace(a_loopfield), 13) != " can't be in ")
	{
		if (PreviousLoopfield != "")
			text2 .= "`n" merge(PreviousLoopfield)
		text2 .= a_loopfield
	}
	else if (PreviousLoopfield != "" and SubStr(a_loopfield, FirstSpace(a_loopfield)+20) = SubStr(PreviousLoopfield[1], FirstSpace(PreviousLoopfield[1])+20))
		PreviousLoopfield.push(a_loopfield)
	else
	{
		if (PreviousLoopfield != "")
			text2 .= "`n" merge(PreviousLoopfield)
		PreviousLoopfield := [a_loopfield]
	}
}
if (PreviousLoopfield != "")
	text2 .= "`n" merge(PreviousLoopfield)
text := text2
;---- omit repetitions of Swordfish/Jellyfish ----
text2 := ""
loop, parse, text, `.
	if (a_loopfield != "")
{
	if (InStr(a_loopfield, "Swordfish") or InStr(a_loopfield, "Jellyfish"))
	{
		FullExplanation := a_loopfield
		loop, parse, FullExplanation, `:
			if (a_index = 1)
				ShortExplanation := a_loopfield
			else if (a_index = 2)
				if InStr(text2, a_loopfield)
					text2 .= ShortExplanation "."
				else
					text2 .= FullExplanation "."
	}
	else
		text2 .= a_loopfield "."
}
text := text2
;---- highlight cells ----
loop, parse, AllCells, -
	if (highlight[a_loopfield] != "green")
		if InStr(text, coord[a_loopfield] "<blue>")
			highlight(a_loopfield, "blue")
		else if InStr(text, coord[a_loopfield] "<red>")
			highlight(a_loopfield, "red")
text := StrReplace(text, "<red>")
text := StrReplace(text, "<blue>")
;---- "number" or "color" ----
if (display = "colors")
	text := StrReplace(text, "number", "color")
;---- uppercase at the beginning of sentences ----
x0 := SubStr(text, 0)
text2 := ""
loop, parse, text, .
	if (a_loopfield != "")
{
	x1 := SubStr(a_loopfield, 1, 1)
	x2 := SubStr(a_loopfield, 2, 1)
	if x1 is lower  ; first character of a color name
		StringUpper, x1, x1
	else if x1 is not alpha  ; space or new line
		if x2 is lower
			StringUpper, x2, x2
	text2 .= x1 x2 SubStr(a_loopfield, 3) "."
}
if (x0 != ".")
	text2 := RTrim(text2, ".")
text := text2
;---- 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)
	}
	if (a_index = 1)
		text2 := line
	else
		text2 .= "`n" line
}
text := text2
;---- tooltip or gui according to number and length of lines ----
LineLenMax := 0
loop, parse, text, `n`r
{
	if (StrLen(a_loopfield) > LineLenMax)
		LineLenMax := StrLen(a_loopfield)
	LineIndex := a_index
}
if (LineIndex <= 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

;---- find all --------------------------------------------------------------------------

FindAll:
gui, 1:+OwnDialogs
loop, parse, AllCells, -
	highlight(a_loopfield, "")
fill("minimum")
if (AnySolution = 0)
	msgbox, There is no solution!
else if (AnySolution = 1)
{
	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 the number/pencil mark in the cell under the mouse cursor with the R, G, B, D or C key.
If there is more than one pencil mark in the cell, press the key repeatedly.
R, G, B, D, C = red, green, blue, default color, mixed color.
Colors can be used to highlight numbers or pencil marks or to improve a Sudoku image.
"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 mouse button. Delete all highlightings with Esc.
The arrow keys also move the mouse cursor.
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
loop, parse, AllCells, -
	highlight(a_loopfield, "")
tooltip
gui, explain:destroy
swap1 := ""
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 (cMix != "")
	color(cMix)
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 := ""
	}
}
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::
if MouseGetCell()
{
	if (highlight[rm cm] != "red")
		highlight(rm cm, "red")
	else
		highlight(rm cm, "")
}
return

^lbutton::
if MouseGetCell()
{
	if (highlight[rm cm] != "green")
		highlight(rm cm, "green")
	else
		highlight(rm cm, "")
}
return

!lbutton::
if MouseGetCell()
{
	if (highlight[rm cm] != "blue")
		highlight(rm cm, "blue")
	else
		highlight(rm cm, "")
}
return

;========================================================================================
;	Main solving subroutines
;========================================================================================

fill(FillWith)
{
	local rc, n
	AllTries := []
	TriesInThisSituation := ""
	%FillWith% := {}
	if (FillWith = "minimum")
	{
		reset := {}
		loop, parse, AllCells, -
			reset[a_loopfield] := number[a_loopfield]
		SinglePossible := {}
	}
	else if (FillWith = "maximum")
	{
		loop, parse, AllCells, -
			set(a_loopfield, reset[a_loopfield])
	}
	loop
	{
		GetPossibleNumbers("fill")
		if (WhatNext["text"] = "no empty cells")
		{
			loop, parse, AllCells, -
				%FillWith%[a_loopfield] := number[a_loopfield]
			AnySolution := 1
			return
		}
		if (WhatNext["text"] = "no possible number" or WhatNext["text"] = "no possible cell")
		{
			if (AllTries.length() = 0)
			{
				AnySolution := 0
				return
			}
			TriesInThisSituation := AllTries.pop()
			rc := SubStr(TriesInThisSituation, -2, 2)
			set(rc, 0)
		}
		else if (WhatNext["text"] = "one possible number" or WhatNext["text"] = "one possible cell")
		{
			rc := WhatNext["rc"]
			n := WhatNext["n"]
			set(rc, n)
			TriesInThisSituation .= "-" rc n
			AllTries.push(TriesInThisSituation)
			TriesInThisSituation := ""
			if (FillWith = "minimum")
			{
				SinglePossible[rc] := 1
				AllSinglePossible := 1
				AllSet := ""
				for i, tries in AllTries
				{
					LastSet := SubStr(tries, -2, 2)
					AllSet .= LastSet "-"
					if (SinglePossible[LastSet] != 1)
					{
						AllSinglePossible := 0
						break
					}
				}
				if (context = "EasyAndSymmetrical" and AllSinglePossible = 1
				and InStr(AllSet, sym1)
				and InStr(AllSet, sym2)
				and InStr(AllSet, sym3)
				and InStr(AllSet, sym4)
				and InStr(AllSet, sym5)
				and InStr(AllSet, sym6)
				and InStr(AllSet, sym7)
				and InStr(AllSet, sym8))
					return
				if (context = "DifficultButNotSymmetrical" and AllSinglePossible = 1 and rc = NextOmit)
					return
			}
		}
		else if (WhatNext["text"] = "several possible numbers")
		{
			rc := WhatNext["rc"]
			if (FillWith = "minimum")
			{
				n := SubStr(WhatNext["n"], 1, 1)
				SinglePossible[rc] := 0
			}
			else if (FillWith = "maximum")
				n := SubStr(WhatNext["n"], 0)
			else if (FillWith = "random")
			{
				n_random := ""
				loop, parse, % WhatNext["n"]
					n_random .= a_loopfield "-"
				sort, n_random, random d-
				n := SubStr(n_random, 1, 1)
			}
			set(rc, n)
			TriesInThisSituation .= "-" rc n
			AllTries.push(TriesInThisSituation)
			TriesInThisSituation := ""
		}
	}
}
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])
	}
}

;----------------------------------------------------------------------------------------

GetPossibleNumbers(caller := "")
{
	local rc, main_index

; CONTENTS:
; list possible numbers for every cell
; possible numbers are step by step eliminated by the following loops and solving techniques:
; loop 4
; {
;	list cells with only two possible numbers
;	- two cells with the same two possible numbers
;	- Y-wings/chains
;	loop units
;	loop numbers
;	{
;		list possible cells for every number in every unit
;		- two numbers with the same two possible cells
;		- two or three possible cells with the same row or column and block
;		- X-wing
;		- empty rectangle
;		- Swordfish/Jellyfish
;	}
; }

	EmptyCells := 0
	loop, parse, AllCells, -
		if (number[a_loopfield] = 0)
	{
		EmptyCells := 1
		break
	}
	if (EmptyCells = 0)
	{
		WhatNext := {text: "no empty cells"}
		return
	}
	GetAllPossibleNumbers()
	PossibleNumbers := AllPossibleNumbers.clone()
	PossibleCells := {}
	explanation := {}  ; explanation[rc n] will be a text string, explaining why n can't be set in cell rc.
	if (caller = "fill")
	{
		AllTries.push(TriesInThisSituation)
		for i, tries in AllTries
			loop, parse, tries, -
				if (a_loopfield != "")
		{
			rc := SubStr(a_loopfield, 1, 2)
			n := SubStr(a_loopfield, 3, 1)
			if (number[rc] = 0)
				PossibleNumbers[rc] := StrReplace(PossibleNumbers[rc], n)
		}
		AllTries.pop()
	}
	loop, parse, AllCells, -
		if (number[a_loopfield] = 0)
	{
		if (context = "CreateImage")
		{
			if InStr(PencilImage, a_loopfield)
				loop, 9
					if not InStr(PencilledNumbers[a_loopfield], a_index)
						PossibleNumbers[a_loopfield] := StrReplace(PossibleNumbers[a_loopfield], a_index)
			if StartOver
			{
				if CancelWait
					gosub CancelNow
				missing := 0
				loop, parse, PencilImage, -
					if (number[a_loopfield] = 0)
				{
					missing += 1
					MissingIndex := a_index
					MissingCell := a_loopfield
				}
				if (missing = 1)
				{
					PencilImageString := ""
					loop, parse, PencilImage, -
						PencilImageString .= number[a_loopfield]
					loop, parse, % PossibleNumbers[MissingCell]
						if InValues(ContinuedImages, SubStr(PencilImageString, 1, MissingIndex-1) . a_loopfield . SubStr(PencilImageString, MissingIndex+1))
							PossibleNumbers[MissingCell] := StrReplace(PossibleNumbers[MissingCell], a_loopfield)
				}
			}
		}
		if FoundOne(a_loopfield)
			return
	}
	loop, 4
	{
		main_index := a_index
		xwing_index := 0
		if (context = "FindOne" 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 ---- (different from "two numbers with the same two possible cells"!)
		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 (context = "FindOne")
						{
							explanation[rc n] := item(n) " can't be in " coord[rc] "<red> because "
							. item(n1) " and " item(n2) " are the only possible numbers in "
							. coord[cell1] "<blue> and " coord[cell2] "<blue>, so they must be there.$"
							loop, parse, % cell1 "-" cell2, -
								loop, 9
									if (a_index != n1 and a_index != n2)
										ExplanationAdd(rc n, a_loopfield a_index)  ; Have there been other possible numbers in a_loopfield?
						}
						PossibleNumbers[rc] := StrReplace(PossibleNumbers[rc], n)
						if FoundOne(a_loopfield)
							return
					}
					break
				}
			}
		}
		;---- three cells with together three possible numbers ----
		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 (context = "FindOne")
							{
								explanation[rc n] := item(n) " can't be in " coord[rc] "<red> because "
								. item(n1) " and " item(n2) " and " item(n3) " are the only possible numbers in "
								. coord[cell1] "<blue> and " coord[cell2] "<blue> and " coord[cell3] "<blue>"
								. ", so they must be there.$"
								loop, parse, % cell1 "-" cell2 "-" cell3, -
									loop, 9
										if (a_index != n1 and a_index != n2 and a_index != n3)
											ExplanationAdd(rc n, a_loopfield a_index)  ; Have there been other possible numbers in a_loopfield?
							}
							PossibleNumbers[rc] := StrReplace(PossibleNumbers[rc], n)
							if FoundOne(a_loopfield)
								return
						}
					}
				}
			}
		}
		if (main_index > 1)  ; try basic/more interesting/less common solving techniques first
			if (context = "FindOne")
		{
			;---- Y-wings/chains ----
			ChainCell := []
			ChainNumber := []
			eliminated := []
			TwoPossibleCells := []
			loop, parse, CellsWith2PossibleNumbers, -
			{
				ChainCell[1] := a_loopfield
				loop, parse, % PossibleNumbers[ChainCell[1]]
				{
					ChainNumber[1] := a_loopfield
					loop, parse, % ConnectedCells[ChainCell[1]], -
						if (PossiblePush(1) = 1)
					{
						ChainCell[2] := ThisChainCell
						ChainNumber[2] := ThesePossibleNumbers
						TwoPossibleCells[2] := TheseTwoPossibleCells
						loop, parse, % ConnectedCells[ChainCell[2]], -
							if (circle("", 1) != 1 and PossiblePush(2) = 1)
						{
							ChainCell[3] := ThisChainCell
							ChainNumber[3] := ThesePossibleNumbers
							eliminated[3] := TheseEliminated
							TwoPossibleCells[3] := TheseTwoPossibleCells
							if (chain(3) = 1)
								return
							loop, parse, % ConnectedCells[ChainCell[3]], -
								if (circle("", 2) != 1 and PossiblePush(3) = 1)
							{
								ChainCell[4] := ThisChainCell
								ChainNumber[4] := ThesePossibleNumbers
								eliminated[4] := TheseEliminated
								TwoPossibleCells[4] := TheseTwoPossibleCells
								if (chain(4) = 1)
									return
								loop, parse, % ConnectedCells[ChainCell[4]], -
									if (circle("", 3) != 1 and PossiblePush(4) = 1)
								{
									ChainCell[5] := ThisChainCell
									ChainNumber[5] := ThesePossibleNumbers
									eliminated[5] := TheseEliminated
									TwoPossibleCells[5] := TheseTwoPossibleCells
									if (chain(5) = 1)
										return
									loop, parse, % ConnectedCells[ChainCell[5]], -
										if (circle("", 4) != 1 and PossiblePush(5) = 1)
									{
										ChainCell[6] := ThisChainCell
										ChainNumber[6] := ThesePossibleNumbers
										eliminated[6] := TheseEliminated
										TwoPossibleCells[6] := TheseTwoPossibleCells
										if (chain(6) = 1)
											return
										loop, parse, % ConnectedCells[ChainCell[6]], -
											if (circle("", 5) != 1 and PossiblePush(6) = 1)
										{
											ChainCell[7] := ThisChainCell
											ChainNumber[7] := ThesePossibleNumbers
											eliminated[7] := TheseEliminated
											TwoPossibleCells[7] := TheseTwoPossibleCells
											if (chain(7) = 1)
												return
											loop, parse, % ConnectedCells[ChainCell[7]], -
												if (circle("", 6) != 1 and PossiblePush(7) = 1)
											{
												ChainCell[8] := ThisChainCell
												ChainNumber[8] := ThesePossibleNumbers
												eliminated[8] := TheseEliminated
												TwoPossibleCells[8] := TheseTwoPossibleCells
												if (chain(8) = 1)
													return
											}
										}
									}
								}
							}
						}
					}
				}
			}
		}
		AllTwoPossibleCells := []
		loop, 27
		{
			u := a_index
			loop, 9
			{
				n := a_index
				AlreadySet := 0
				PossibleCells[u n] := []
				loop, parse, % UnitCells[u], -
				{
					if (number[a_loopfield] = n)
					{
						AlreadySet := 1
						break
					}
					else if (number[a_loopfield] = 0)
					{
						if InStr(PossibleNumbers[a_loopfield], n)
							PossibleCells[u n].push(a_loopfield)
					}
				}
				if (AlreadySet = 0 and PossibleCells[u n].length() = 0)
				{
					WhatNext := {u: u, n: n, text: "no possible cell"}
					return
				}
				else if (PossibleCells[u n].length() = 1)
				{
					rc := PossibleCells[u n][1]
					loop, 9
						if (a_index != n)
							Eliminate(rc, a_index)
					WhatNext := {u: u, rc: rc, n: n, text: "one possible cell"}
					return
				}
				else
				{
					if (PossibleCells[u n].length() = 2)
						AllTwoPossibleCells.push({TwoCells: PossibleCells[u n][1] "-" PossibleCells[u n][2], u: u, n: n})
					;---- two numbers with the same two possible cells ---- (different from "two cells with the same two possible numbers"!)
					if (PossibleCells[u n].length() = 2)
						loop, % n-1
							if (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
						for i, cell in PossibleCells[u n]
						{
							if (context = "FindOne")
								loop, parse, % PossibleNumbers[cell]
									if (a_loopfield != n and a_loopfield != n2)
							{
								n3 := a_loopfield
								rc1 := PossibleCells[u n][1]
								rc2 := PossibleCells[u n][2]
								explanation[cell n3] := "In " unit[u] ", " item(n2) " and " item(n) " can only be in " coord[rc1] "<blue> and "
								. coord[rc2] "<blue>, so they will be there, and other numbers can't be there.$"
								loop, parse, % UnitCells[u], -
									if (a_loopfield != rc1 and a_loopfield != rc2)
								{
									ExplanationAdd(cell n3, a_loopfield n2)
									ExplanationAdd(cell n3, a_loopfield n)
								}
							}
							loop, 9
								if (a_index != n and a_index != n2)
									Eliminate(cell, a_index)
						}
						break
					}
					;---- two or three possible cells with the same row or column and block ----
					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 (number[a_loopfield] = 0 and not InValues(PossibleCells[u n], a_loopfield)
							and InStr(PossibleNumbers[a_loopfield], n))
						{
							rc := a_loopfield
							if (context = "FindOne")
							{
								text := item(n) " can't be in " coord[rc] "<red> because in " unit[u] " it must be in "
								for i, cell in PossibleCells[u n]
									text .= coord[cell] "<blue> or "
								text := SubStr(text, 1, -4)
								explanation[rc n] := text ".$"
								loop, parse, % UnitCells[u], -
									if not InStr(UnitCells[u2], a_loopfield)
										ExplanationAdd(rc n, a_loopfield n)
							}
							Eliminate(rc, n)
							if FoundOne(rc)
								return
						}
						break
					}
					if (context = "FindOne")
					{
						;---- X-wing ----
						if (PossibleCells[u n].length() = 2)
						{
							A := PossibleCells[u n][1]
							B := PossibleCells[u n][2]
							loop, % u-1  ; iterations u+1 to 27 would get earlier PossibleCells
							{
								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)
								{
									positions := A B C D "/" B A C D "/" A B D C "/" B A D C
									loop, parse, positions, /
									{
										uCell1 := SubStr(a_loopfield, 1, 2)
										uCell2 := SubStr(a_loopfield, 3, 2)
										u2Cell1 := SubStr(a_loopfield, 5, 2)
										u2Cell2 := SubStr(a_loopfield, -1)
										if (InStr(ConnectedCells[uCell1], u2Cell1)
										and not InStr(ConnectedCells[uCell2], u2Cell1)
										and not InStr(ConnectedCells[u2Cell2], uCell1))
										{
											loop, parse, % ConnectedCells[uCell2], -
												if (number[a_loopfield] = 0
												and InStr(ConnectedCells[u2Cell2], a_loopfield)
												and InStr(PossibleNumbers[a_loopfield], n)
												and (xwing_index < 3 or main_index > 1))
												{
													xwing_index += 1
													rc := a_loopfield
													if (context = "FindOne")
													{
														explanation[rc n] := "
(
" item(n) " can't be in " coord[rc] "<red> because of the ""X-Wing"" " coord[u2Cell1] " - " coord[u2Cell2] "/" coord[uCell1] " - " coord[uCell2] ":
- If in " unit[u2] " " item(n) " is in " coord[u2Cell1] "<blue>, then in " unit[u] " it must be in " coord[uCell2] "<blue>.
- If in " unit[u] " " item(n) " is in " coord[uCell1] "<blue>, then in " unit[u2] " it must be in " coord[u2Cell2] "<blue>.
Either way " item(n) " can't be in " coord[rc] " because " coord[rc] " is connected with both " coord[uCell2] " and " coord[u2Cell2] ".$
)"
														loop, parse, % UnitCells[u], -
															if not InValues(PossibleCells[u n], a_loopfield)
																ExplanationAdd(rc n, a_loopfield n)
														loop, parse, % UnitCells[u2], -
															if not InValues(PossibleCells[u2 n], a_loopfield)
																ExplanationAdd(rc n, a_loopfield n)
													}
													Eliminate(rc, n)
													if FoundOne(rc)
														return
												}
										}
									}
								}
							}
						}
						;---- empty rectangle ----
						if (UnitType[u] = "block" and PossibleCells[u n].length() >= 3 and PossibleCells[u n].length() <= 5
						or main_index > 1 and PossibleCells[u n].length() >= 2 and PossibleCells[u n].length() <= 6)
						{
							loop, 27
								if (UnitType[a_index] != UnitType[u])
							{
								road1 := a_index
								loop, 27
									if (a_index > road1 and UnitType[a_index] != UnitType[u])
								{
									road2 := a_index
									crossroads := UnitCells[road1] "-" UnitCells[road2]
									AllInCrossroads := 1
									for i, cell in PossibleCells[u n]
										if not InStr(crossroads, cell)
									{
										AllInCrossroads := 0
										break
									}
									if (AllInCrossroads = 1)
									{
										NotInRoad1 := 0
										NotInRoad2 := 0
										for i, cell in PossibleCells[u n]
										{
											if not InStr(UnitCells[road1], cell)
												NotInRoad1 := 1
											else if not InStr(UnitCells[road2], cell)
												NotInRoad2 := 1
										}
										if (NotInRoad1 = 1 and NotInRoad2 = 1)
										{
											; At least one possible cell for n in u is only in road1, and at least one is only in road2.
											ChainCell := []
											ChainNumber := []
											eliminated := []
											TwoPossibleCells := []
											loop, parse, % UnitCells[road1], -																; road1
												if (not InStr(UnitCells[u], a_loopfield)
												and InStr(CellsWith2PossibleNumbers, a_loopfield)
												and InStr(PossibleNumbers[a_loopfield], n))
											{
												ChainCell[1] := a_loopfield
												ChainNumber[1] := OtherNumber(PossibleNumbers[ChainCell[1]], n)
												loop, parse, % UnitCells[road2], -															; road2
													if (not InStr(UnitCells[u], a_loopfield)
													and InStr(CellsWith2PossibleNumbers, a_loopfield)
													and InStr(PossibleNumbers[a_loopfield], n))
												{
													ChainCell[2] := a_loopfield
													ChainNumber[2] := OtherNumber(PossibleNumbers[ChainCell[2]], n)
													if (EmptyRectangle(2) = 1)
														return
													loop, parse, % ConnectedCells[ChainCell[1]], -											; road1
														if (circle(PossibleCells[u n], 2) != 1 and PossiblePush(1, road1) = 1)
													{
														ChainCell[3] := ThisChainCell
														ChainNumber[3] := ThesePossibleNumbers
														eliminated[3] := TheseEliminated
														TwoPossibleCells[3] := TheseTwoPossibleCells
														if (EmptyRectangle(3) = 1)
															return
														loop, parse, % ConnectedCells[ChainCell[2]], -										; road2
															if (circle(PossibleCells[u n], 3) != 1 and PossiblePush(2, road2) = 1)
														{
															ChainCell[4] := ThisChainCell
															ChainNumber[4] := ThesePossibleNumbers
															eliminated[4] := TheseEliminated
															TwoPossibleCells[4] := TheseTwoPossibleCells
															if (EmptyRectangle(4) = 1)
																return
															loop, parse, % ConnectedCells[ChainCell[3]], -									; road1
																if (circle(PossibleCells[u n], 4) != 1 and PossiblePush(3, road1) = 1)
															{
																ChainCell[5] := ThisChainCell
																ChainNumber[5] := ThesePossibleNumbers
																eliminated[5] := TheseEliminated
																TwoPossibleCells[5] := TheseTwoPossibleCells
																if (EmptyRectangle(5) = 1)
																	return
																loop, parse, % ConnectedCells[ChainCell[4]], -								; road2
																	if (circle(PossibleCells[u n], 5) != 1 and PossiblePush(4, road2) = 1)
																{
																	ChainCell[6] := ThisChainCell
																	ChainNumber[6] := ThesePossibleNumbers
																	eliminated[6] := TheseEliminated
																	TwoPossibleCells[6] := TheseTwoPossibleCells
																	if (EmptyRectangle(6) = 1)
																		return
																	loop, parse, % ConnectedCells[ChainCell[5]], -							; road1
																		if (circle(PossibleCells[u n], 6) != 1 and PossiblePush(5, road1) = 1)
																	{
																		ChainCell[7] := ThisChainCell
																		ChainNumber[7] := ThesePossibleNumbers
																		eliminated[7] := TheseEliminated
																		TwoPossibleCells[7] := TheseTwoPossibleCells
																		if (EmptyRectangle(7) = 1)
																			return
																		loop, parse, % ConnectedCells[ChainCell[6]], -						; road2
																			if (circle(PossibleCells[u n], 7) != 1 and PossiblePush(6, road2) = 1)
																		{
																			ChainCell[8] := ThisChainCell
																			ChainNumber[8] := ThesePossibleNumbers
																			eliminated[8] := TheseEliminated
																			TwoPossibleCells[8] := TheseTwoPossibleCells
																			if (EmptyRectangle(8) = 1)
																				return
																		}
																	}
																}
															}
														}
													}
												}
											}
										}
									}
								}
							}
						}
						;---- Swordfish/Jellyfish ----
						if (main_index > 1)  ; don't catch too many fish
							if (u <= 18 and PossibleCells[u n].length() >= 2 and PossibleCells[u n].length() <= 4)
						{
							line := []  ; Lines can be rows or columns.
							line[1] := u
							loop, 18
							{
								line[2] := a_index
								if fishnet(2) = 2
									loop, 18
								{
									line[3] := a_index
									if fishnet(3) = 1
										return
									else if fishnet(3) = 2
									{
										loop, 18
										{
											line[4] := a_index
											if fishnet(4) = 1
												return
										}
									}
								}
							}
						}
					}
				}
			}
		}
	}
	min := 10
	loop, parse, AllCells, -
		if (number[a_loopfield] = 0)
	{
		if (StrLen(PossibleNumbers[a_loopfield]) = 2)
		{
			rc := a_loopfield
			break
		}
		else if (StrLen(PossibleNumbers[a_loopfield]) < min)
		{
			rc := a_loopfield
			min := StrLen(PossibleNumbers[a_loopfield])
		}
	}
	WhatNext := {rc: rc, n: PossibleNumbers[rc], text: "several possible numbers"}
}

;========================================================================================
;	Main subroutines for GetPossibleNumbers
;========================================================================================

chain(ChainIndex)
{
	global
	if (ChainNumber[ChainIndex] = "")  ; => ChainNumber[1] in ChainCell[1] can't have been right in the first place
	{
		Eliminate(ChainCell[1], ChainNumber[1])
		if (context = "FindOne")
		{
			text1 := item(ChainNumber[1]) " can't be in " coord[ChainCell[1]] " because of the chain " coord[ChainCell[1]]
			text2 := "If " coord[ChainCell[1]] " is " item(ChainNumber[1])
			loop, %ChainIndex%
				if (a_index > 1 and a_index < ChainIndex)
			{
				text1 .= " - " coord[ChainCell[a_index]] "<blue>"
				text2 .= ", then " coord[ChainCell[a_index]] "<blue> must be " item(ChainNumber[a_index])
				if (TwoPossibleCells[a_index] != "")
					text2 .= " (because it's the only possible cell left in " unit[TwoPossibleCells[a_index]["u"]] ")"
			}
			explanation[ChainCell[1] ChainNumber[1]] := text1 ": " text2 ", and there is no possible number left in "
			. coord[ChainCell[ChainIndex]] "<red>. So " item(ChainNumber[1]) " in " coord[ChainCell[1]] " can't be right in the first place.$"
			loop, %ChainIndex%
			{
				i := a_index
				if (TwoPossibleCells[i] = "")
				{
					loop, 9
						if (not InStr(PossibleNumbers[ChainCell[i]], a_index) and not InStr(eliminated[i], a_index))
							ExplanationAdd(ChainCell[1] ChainNumber[1], ChainCell[i] a_index)
				}
				else loop, parse, % UnitCells[TwoPossibleCells[i]["u"]], -
					if (a_loopfield != TwoPossibleCells[i]["rc1"] and a_loopfield != TwoPossibleCells[i]["rc2"])
						ExplanationAdd(ChainCell[1] ChainNumber[1], a_loopfield TwoPossibleCells[i]["n"])
			}
		}
		WhatNext := {rc: ChainCell[1], n: PossibleNumbers[ChainCell[1]], text: "one possible number"}
		return 1
	}
	else loop, parse, AllCells, -
		if (number[a_loopfield] = 0
		and InStr(ConnectedCells[ChainCell[1]], a_loopfield)
		and InStr(ConnectedCells[ChainCell[ChainIndex]], a_loopfield)
		and circle("", ChainIndex) != 1
		and PossiblePush("1B") >= 1)  ; B means the other possible number.
	{
		EliminateIn := ThisChainCell
		Eliminated1 := TheseEliminated
		TwoPossibleCells1 := TheseTwoPossibleCells
		if (PossiblePush(ChainIndex) >= 1)
		{
			Eliminated2 := TheseEliminated
			TwoPossibleCells2 := TheseTwoPossibleCells
			EliminateWhat := ""
			loop, parse, % PossibleNumbers[EliminateIn]
				if (InStr(Eliminated1, a_loopfield) and InStr(Eliminated2, a_loopfield))
			{
				EliminateWhat := a_loopfield
				break
			}
			if (EliminateWhat != "")
			{
				if (context = "FindOne")
				{
					text := item(EliminateWhat) " can't be in " coord[EliminateIn] "<red> because of the chain " coord[ChainCell[1]] "<blue>"
					loop, %ChainIndex%
						if (a_index > 1)
							text .= " - " coord[ChainCell[a_index]] "<blue>"
					text .= ": If " coord[ChainCell[1]] " is " item(OtherNumber(PossibleNumbers[ChainCell[1]], ChainNumber[1])) ", then " coord[EliminateIn]
					if (TwoPossibleCells1 = "")
						text .= " can't be " item(EliminateWhat) ". "
					else
						text .= " must be " TwoPossibleCells1["n"] " (because it's the only possible cell left in " unit[TwoPossibleCells1["u"]] "). "
					text .= "Else, if " coord[ChainCell[1]] " is " item(ChainNumber[1])
					loop, %ChainIndex%
						if (a_index > 1)
					{
						text .= ", then " coord[ChainCell[a_index]] " must be " item(ChainNumber[a_index])
						if (TwoPossibleCells[a_index] != "")
							text .= " (because it's the only possible cell left in " unit[TwoPossibleCells[a_index]["u"]] ")"
					}
					if (TwoPossibleCells2 = "")
						text .= ", and again, " coord[EliminateIn] " can't be " item(EliminateWhat) "."
					else
						text .= ", then " coord[EliminateIn] " must be " TwoPossibleCells2["n"] " (because it's the only possible cell left in " unit[TwoPossibleCells2["u"]] ")."
					explanation[EliminateIn EliminateWhat] := text "$"
					loop, %ChainIndex%
					{
						i := a_index
						if (TwoPossibleCells[i] = "")
						{
							loop, 9
								if (not InStr(PossibleNumbers[ChainCell[i]], a_index) and not InStr(eliminated[i], a_index))
									ExplanationAdd(EliminateIn EliminateWhat, ChainCell[i] a_index)
						}
						else loop, parse, % UnitCells[TwoPossibleCells[i]["u"]], -
							if (a_loopfield != TwoPossibleCells[i]["rc1"] and a_loopfield != TwoPossibleCells[i]["rc2"])
								ExplanationAdd(EliminateIn EliminateWhat, a_loopfield TwoPossibleCells[i]["n"])
					}
					For i, TwoPossibleCells in [TwoPossibleCells1, TwoPossibleCells2]
						loop, parse, % UnitCells[TwoPossibleCells["u"]], -
							if (a_loopfield != TwoPossibleCells["rc1"] and a_loopfield != TwoPossibleCells["rc2"])
								ExplanationAdd(EliminateIn EliminateWhat, a_loopfield TwoPossibleCells["n"])
				}
				Eliminate(EliminateIn, EliminateWhat)
				if FoundOne(EliminateIn)
					return 1
			}
		}
	}
}

;---------------------------------------------------------------------------------------------

EmptyRectangle(ChainIndex)
{
	global
	if (ChainNumber[ChainIndex] = "")
	{
		if mod(ChainIndex, 2) = 0  ; road2 leads to ChainCell[2], ChainCell[4], ChainCell[6]
		{
			WrongRoad := road2
			RightRoad := road1
			RightChainCell1 := ChainCell[1]
		}
		else
		{
			WrongRoad := road1
			RightRoad := road2
			RightChainCell1 := ChainCell[2]
		}
		Eliminate(RightChainCell1, n)
		if (context = "FindOne")
		{
			TextCrossRoads := ""
			for i, cell in PossibleCells[u n]
				TextCrossRoads .= " or " coord[cell] "<blue>"
			TextCrossRoads := SubStr(TextCrossRoads, 5)
			TextWrongChain := ""
			loop, % ChainIndex-1
				if (mod(a_index, 2) = mod(ChainIndex, 2))
			{
				TextWrongChain .= ", then " coord[ChainCell[a_index]] "<blue> must be " item(ChainNumber[a_index])
				if (TwoPossibleCells[a_index] != "")
					TextWrongChain .= " (because it's the only possible cell left in " unit[TwoPossibleCells[a_index]["u"]] ")"
			}
			if (UnitType[u] = "block")
				text := item(n) " can't be in " coord[RightChainCell1] "<red> because of the ""empty rectangle"" in " unit[u] ": "
			else
				text := item(n) " can't be in " coord[RightChainCell1] "<red> because of " item(n) " in " unit[u] 
				. " (basically like an ""empty rectangle"", but within a " UnitType[u] ", not within a block): "
			explanation[RightChainCell1 n] := text "In " unit[u] ", " item(n) " must be in " TextCrossRoads ", that is, in " unit[road1] " or " unit[road2] ". "
			. "If " item(n) " is in " unit[WrongRoad] TextWrongChain ", and there is no possible number left in "
			. coord[ChainCell[ChainIndex]] "<red>. So " item(n) " in " unit[WrongRoad] " can't be right in the first place, "
			. item(n) " must be in " unit[RightRoad] " and therefore can't be in " coord[RightChainCell1] ".$"
			loop, parse, % UnitCells[u], -
				if (not InStr(UnitCells[road1], a_loopfield) and not InStr(UnitCells[road2], a_loopfield))
					ExplanationAdd(RightChainCell1 n, a_loopfield n)  ; no possible cells for n in u outside the crossroads
			loop, %ChainIndex%
			{
				i := a_index
				if (mod(a_index, 2) = mod(ChainIndex, 2) and TwoPossibleCells[i] = "")
				{
					loop, 9
						if (not InStr(PossibleNumbers[ChainCell[i]], a_index) and not InStr(eliminated[i], a_index))
							ExplanationAdd(RightChainCell1 n, ChainCell[i] a_index)  ; no other possible numbers in the normal chain cells
				}
				else loop, parse, % UnitCells[TwoPossibleCells[i]["u"]], -
					if (a_loopfield != TwoPossibleCells[i]["rc1"] and a_loopfield != TwoPossibleCells[i]["rc2"])
						ExplanationAdd(RightChainCell1 n, a_loopfield TwoPossibleCells[i]["n"])  ; no other possible cells for the number in the TwoPossibleCells chain cells
			}
		}
		WhatNext := {rc: RightChainCell1, n: PossibleNumbers[RightChainCell1], text: "one possible number"}
		return 1
	}
	else
	{
		if mod(ChainIndex, 2) = 0	; road2 leads to ChainCell[2], ChainCell[4], ChainCell[6]
		{
			i1 := ChainIndex-1		; i1 is the index of road1
			i2 := ChainIndex		; i2 is the index of road2
		}
		else
		{
			i1 := ChainIndex
			i2 := ChainIndex-1
		}
		loop, parse, AllCells, -
			if (number[a_loopfield] = 0
			and InStr(ConnectedCells[ChainCell[i1]], a_loopfield)
			and InStr(ConnectedCells[ChainCell[i2]], a_loopfield)
			and circle(PossibleCells[u n], ChainIndex) != 1
			and PossiblePush(i1, road1) >= 1)
		{
			EliminateIn := ThisChainCell
			Eliminated1 := TheseEliminated
			TwoPossibleCells1 := TheseTwoPossibleCells
			if (PossiblePush(i2, road2) >= 1)
			{
				Eliminated2 := TheseEliminated
				TwoPossibleCells2 := TheseTwoPossibleCells
				EliminateWhat := ""
				loop, parse, % PossibleNumbers[EliminateIn]
					if (InStr(Eliminated1, a_loopfield) and InStr(Eliminated2, a_loopfield))
				{
					EliminateWhat := a_loopfield
					break
				}
				if (EliminateWhat != "")
				{
					if (context = "FindOne")
					{
						if (UnitType[u] = "block")
							text := item(EliminateWhat) " can't be in " coord[EliminateIn] "<red> because of the ""empty rectangle"" in " unit[u] ": "
						else
							text := item(EliminateWhat) " can't be in " coord[EliminateIn] "<red> because of " item(n) " in " unit[u] 
							. " (basically like an ""empty rectangle"", but within a " UnitType[u] ", not within a block): "
						text .= "In " unit[u] ", " item(n) " must be in "
						TextCrossRoads := ""
						for i, cell in PossibleCells[u n]
							TextCrossRoads .= " or " coord[cell] "<blue>"
						TextCrossRoads := SubStr(TextCrossRoads, 5)
						text .= TextCrossRoads ", that is, in " unit[road1] " or " unit[road2] ". "
						. "If " item(n) " is in " unit[road1]
						loop, %i1%
							if mod(a_index, 2) != 0  ; road1 leads to ChainCell[1], ChainCell[3], ChainCell[5]
						{
							text .= ", then " coord[ChainCell[a_index]] "<blue> must be " item(ChainNumber[a_index])
							if (TwoPossibleCells[a_index] != "")
								text .= " (because it's the only possible cell left in " unit[TwoPossibleCells[a_index]["u"]] ")"
						}
						if (TwoPossibleCells1 = "")
							text .= ", and " coord[EliminateIn] " can't be " item(EliminateWhat) ". "
						else
							text .= ", and " coord[EliminateIn] " must be " TwoPossibleCells1["n"] " (because it's the only possible cell left in " unit[TwoPossibleCells1["u"]] "). "
						text .= "Else, if " item(n) " is in " unit[road2]
						loop, %i2%
							if mod(a_index, 2) = 0  ; road2 leads to ChainCell[2], ChainCell[4], ChainCell[6]
						{
							text .= ", then " coord[ChainCell[a_index]] "<blue> must be " item(ChainNumber[a_index])
							if (TwoPossibleCells[a_index] != "")
								text .= " (because it's the only possible cell left in " unit[TwoPossibleCells[a_index]["u"]] ")"
						}
						if (TwoPossibleCells2 = "")
							text .= ", and again, " coord[EliminateIn] " can't be " item(EliminateWhat) "."
						else
							text .= ", and " coord[EliminateIn] " must be " TwoPossibleCells2["n"] " (because it's the only possible cell left in " unit[TwoPossibleCells2["u"]] ")."
						explanation[EliminateIn EliminateWhat] := text "$"
						loop, parse, % UnitCells[u], -
							if (not InStr(UnitCells[road1], a_loopfield) and not InStr(UnitCells[road2], a_loopfield))
								ExplanationAdd(EliminateIn EliminateWhat, a_loopfield n)  ; no possible cells for n in u outside the crossroads
						loop, %ChainIndex%
						{
							i := a_index
							if (TwoPossibleCells[i] = "")
							{
								loop, 9
									if (not InStr(PossibleNumbers[ChainCell[i]], a_index) and not InStr(eliminated[i], a_index))
										ExplanationAdd(EliminateIn EliminateWhat, ChainCell[i] a_index)  ; no other possible numbers in the normal chain cells
							}
							else loop, parse, % UnitCells[TwoPossibleCells[i]["u"]], -
								if (a_loopfield != TwoPossibleCells[i]["rc1"] and a_loopfield != TwoPossibleCells[i]["rc2"])
									ExplanationAdd(EliminateIn EliminateWhat, a_loopfield TwoPossibleCells[i]["n"])  ; no other possible cells for the number in the TwoPossibleCells chain cells
						}
						For i, TwoPossibleCells in [TwoPossibleCells1, TwoPossibleCells2]
							loop, parse, % UnitCells[TwoPossibleCells["u"]], -
								if (a_loopfield != TwoPossibleCells["rc1"] and a_loopfield != TwoPossibleCells["rc2"])
									ExplanationAdd(EliminateIn EliminateWhat, a_loopfield TwoPossibleCells["n"])
					}
					Eliminate(EliminateIn, EliminateWhat)
					if FoundOne(EliminateIn)
						return 1
				}
			}
		}
	}
}

;----------------------------------------------------------------------------------------

PossiblePush(i, u := "")
{
	global
	if (i = "1B")
	{
		i := 1
		PushingNumber := OtherNumber(PossibleNumbers[ChainCell[1]], ChainNumber[1])
	}
	else
		PushingNumber := ChainNumber[i]
	ThisChainCell := a_loopfield
	if (number[ThisChainCell] != 0)
		return 0
	ThesePossibleNumbers := PossibleNumbers[ThisChainCell]
	TheseEliminated := ""  ; TheseEliminated will be needed to avoid double explanations, it may contain already eliminated numbers.
	TheseTwoPossibleCells := ""
	if (u != "" and InStr(UnitCells[u], ThisChainCell))
	{
		ThesePossibleNumbers := StrReplace(ThesePossibleNumbers, n)
		TheseEliminated .= n
	}
	ixxx := ""
	loop, % i-1
		if (u = "" or mod(a_index, 2) = mod(i, 2))
			ixxx .= a_index
	; ixxx is the string of the indices of the chain cells before the pushing cell
	; that might change the possible numbers of the next chain cell:
	; - normally all chain cells before the pushing cell
	; - in empty rectangle chains the chain cells of the same road before the pushing cell
	loop, parse, ixxx
		if (InStr(ConnectedCells[ChainCell[a_loopfield]], ThisChainCell))
	{
		ThesePossibleNumbers := StrReplace(ThesePossibleNumbers, ChainNumber[a_loopfield])
		TheseEliminated .= ChainNumber[a_loopfield]
	}
	if (ThesePossibleNumbers = PushingNumber)
	{
		ThesePossibleNumbers := ""
		TheseEliminated .= PushingNumber
		return 1
	}
	else if (InStr(ThesePossibleNumbers, PushingNumber) and StrLen(ThesePossibleNumbers) = 2)
	{
		ThesePossibleNumbers := OtherNumber(ThesePossibleNumbers, PushingNumber)
		TheseEliminated .= PushingNumber
		return 1
	}
	else
	{
		if (ChainCell[i] < ThisChainCell)
			TwoCells := ChainCell[i] "-" ThisChainCell
		else
			TwoCells := ThisChainCell "-" ChainCell[i]
		for i, TwoPossibleCells in AllTwoPossibleCells
			if (TwoPossibleCells["TwoCells"] = TwoCells and TwoPossibleCells["n"] != PushingNumber)
		{
			TheseEliminated .= StrReplace(ThesePossibleNumbers, TwoPossibleCells["n"])  ; all except TwoPossibleCells["n"]
			ThesePossibleNumbers := TwoPossibleCells["n"]
			TheseTwoPossibleCells := {rc1: ChainCell[i], rc2: ThisChainCell, u: TwoPossibleCells["u"], n: TwoPossibleCells["n"]}
			return 1
		}
	}
	if (InStr(ThesePossibleNumbers, PushingNumber) and StrLen(ThesePossibleNumbers) > 2)
	{
		ThesePossibleNumbers := StrReplace(ThesePossibleNumbers, PushingNumber)
		TheseEliminated .= PushingNumber
		return 2
	}
}

;---------------------------------------------------------------------------------------------

fishnet(LineIndex)
{
	global
	LineType := UnitType[line[1]]
	if (UnitType[line[LineIndex]] != LineType)		; Lines must be all rows or columns.
		return 0
	loop, % LineIndex-1
		if (line[a_index] = line[LineIndex])		; Lines must be different.
			return 0
	if (PossibleCells[line[LineIndex] n].length() = 0)
		return 0
	if (PossibleCells[line[LineIndex] n].length() != 2 
	and PossibleCells[line[LineIndex] n].length() != 3 
	and PossibleCells[line[LineIndex] n].length() != 4)
		return 0
	lines := ""
	AllPossibleCells := []
	loop, %LineIndex%
	{
		lines .= line[a_index] "-"
		for i, cell in PossibleCells[line[a_index] n]
			AllPossibleCells.push(cell)
	}
	lines := RTrim(lines, "-")
	diagons := ""
	DiagonsLen := 0
	for i, rc in AllPossibleCells
	{
		d := diagon(rc)
		if not InStr(diagons, d)
		{
			diagons .= d "-"
			DiagonsLen += 1
		}
	}
	diagons := RTrim(diagons, "-")
	if ((LineIndex = 3 or LineIndex = 4) and DiagonsLen = LineIndex)
	{
		loop, parse, diagons, -
		{
			d := a_loopfield
			loop, parse, % UnitCells[d], -
				if (number[a_loopfield] = 0 and InStr(PossibleNumbers[a_loopfield], n) and not InValues(AllPossibleCells, a_loopfield))
			{
				rc := a_loopfield
				if (context = "FindOne")
				{
					if (DiagonsLen = 3)
						fish := " ""Swordfish"" "
					else if (DiagonsLen = 4)
						fish := " ""Jellyfish"" "
					;--------------------
					sort, lines, n d-
					ListLines := ""
					TextLines := ""
					loop, parse, lines, -
					{
						if (LineType = "row")
							ListLines .= ", " a_loopfield
						else
							ListLines .= ", " letter[a_loopfield-9]
						TextLines .= " and " unit[a_loopfield]
					}
					ListLines := SubStr(ListLines, 3)
					TextLines := SubStr(TextLines, 5)
					;--------------------
					sort, diagons, n d-
					ListDiagons := ""
					TextDiagons := ""
					loop, parse, diagons, -
					{
						if (LineType = "row")
							ListDiagons .= ", " letter[a_loopfield-9]
						else
							ListDiagons .= ", " a_loopfield
						TextDiagons .= " or " unit[a_loopfield]
					}
					ListDiagons := SubStr(ListDiagons, 3)
					TextDiagons := SubStr(TextDiagons, 4)
					;--------------------
					TextPossibleCells := ""
					loop, parse, lines, -
					{
						l := a_loopfield
						loop, parse, diagons, -
						{
							d := a_loopfield
							if (LineType = "row")
								LinesDiagonsGridCell := l (d-9)
							else
								LinesDiagonsGridCell := d (l-9)
							if InStr(AllPossibleNumbers[LinesDiagonsGridCell], n)
								TextPossibleCells .= " or " coord[LinesDiagonsGridCell] "<blue>"
						}
					}
					TextPossibleCells := SubStr(TextPossibleCells, 4)
					;--------------------
					explanation[rc n] := item(n) " can't be in " coord[rc] "<red> because of the" fish "in " LineType " " ListLines "/" UnitType[d] " " ListDiagons ": "
					. "In " TextLines ", " item(n) " must be in " TextPossibleCells "; that's all in " TextDiagons ", and that's one " item(n) " for each " UnitType[d]
					. ", so there can't be any more " item(n) "s in these " UnitType[d] "s.$"
					; explain: no other diagons
					loop, %LineIndex%
						loop, parse, % UnitCells[line[a_index]], -
							if not InStr(diagons, diagon(a_loopfield))
								ExplanationAdd(rc n, a_loopfield n)
				}
				Eliminate(rc, n)
				if FoundOne(rc)
					return 1
			}
		}
	}
	else if (LineIndex < 4 and DiagonsLen <= 4)  ; there might still be a Swordfish or Jellyfish
		return 2
}

;========================================================================================
;	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)
		{
			SizeAndPosition(0.5-zoom)
			gui, %win2%:show, % "y" screenbottom-win2height
			gui, 1:show, y0 w%wGui% h%wGui%
		}
		else if (ZoomFitWidth > ZoomFitHeight)
		{
			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
		{
			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
		}
	}
}

;----------------------------------------------------------------------------------------

background(color)
{
	global
	color := hex(color)
	if (cBackground != color)
	{
		cBackground := color
		gui, 1:color, %cBackground%
		cDefault := ColorDifference(cBackground, "black")>ColorDifference(cBackground, "white") ? "black" : "0xDFDFDF"
		captions(cDefault)
		if (display = "numbers")
		{
			cBackgroundForNumbers := cBackground
			tGreen := 0  ; t for "tolerance"
			tOrange := 0
			tBackground := 0
			if (ColorDifference("green", cBackground) < 300)
				tGreen := 300-ColorDifference("green", cBackground)
			if (ColorDifference("orange", cBackground) < 200)
				tOrange := 200-ColorDifference("orange", cBackground)
			if (ColorDifference(cBackground, 0) < ColorDifference("green", 0))
				tBackground := ColorDifference("green", 0)-ColorDifference(cBackground, 0)
			if (ColorDifference("green", cBackground) > 300 and ColorDifference(cBackground, 0) > ColorDifference("green", 0)
			or ColorDifference("green", cBackground) > 250 and ColorDifference(cBackground, 0) > ColorDifference("green", 0)-100 and 2*tGreen+tBackground < 2*tOrange)
				cFixed := "green"
			else
				cFixed := "orange"
			if (ColorDifference("orange", cBackground) > 200 or cFixed = "orange")
				cAutoPencilled := "orange"
			else
				cAutoPencilled := "green"
		}
	}
}

;----------------------------------------------------------------------------------------

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")
}

;----------------------------------------------------------------------------------------

circle(PossibleCells, ChainIndex)
{
	global
	for i, cell in PossibleCells
		if (cell = a_loopfield)
			return 1
	loop, %ChainIndex%
		if (ChainCell[a_index] = a_loopfield)
			return 1
}

;----------------------------------------------------------------------------------------

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
	}
}

;----------------------------------------------------------------------------------------

ColorDifference(color1, color2)
{
	global
	split(color1, "1")
	split(color2, "2")
	dRed := red1-red2
	dGreen := green1-green2
	dBlue := blue1-blue2
	mRed := (red1+red2)/2
	if (color2 = 0)
		return floor(sqrt(3*red1**2+6*green1**2+blue1**2))
	else if (color2 = "white")
		return floor(sqrt(3*(255-red1)**2+6*(255-green1)**2+(255-blue1)**2))
	else if (mRed < 128)
		return floor(sqrt(2*dRed**2+4*dGreen**2+3*dBlue**2))
	else
		return floor(sqrt(3*dRed**2+4*dGreen**2+2*dBlue**2))
}

;----------------------------------------------------------------------------------------

diagon(rc)
{
	global LineType
	if (LineType = "row")
		diagon := SubStr(rc, 2, 1) + 9
	else
		diagon := SubStr(rc, 1, 1)
	return diagon
}

;----------------------------------------------------------------------------------------

Eliminate(rc, n1, n2 := "")
{
	global PossibleNumbers, PossibleCells, ValueIndex
	for i, n in [n1, n2]
		if (n != "")
	{
		PossibleNumbers[rc] := StrReplace(PossibleNumbers[rc], n)
		row := SubStr(rc, 1, 1)
		column := SubStr(rc, 2, 1) + 9
		for i2, u in [row, column, block[rc]]
			if InValues(PossibleCells[u n], rc)
				PossibleCells[u n].RemoveAt(ValueIndex)
		for i2, TwoPossibleCells in AllTwoPossibleCells
			if (InStr(TwoPossibleCells["TwoCells"], rc) and TwoPossibleCells["n"] = n)
				AllTwoPossibleCells.RemoveAt(i2)
	}
}

;----------------------------------------------------------------------------------------

ExplanationAdd(rcn1, rcn2)
{
	global
	if (rcn1 = "text")
	{
		if (explanation[rcn2] != "" and not InStr(text, explanation[rcn2]))
			text .= "`n" explanation[rcn2]
	}
	else
	{
		if (explanation[rcn2] != "" and not InStr(explanation[rcn1], explanation[rcn2]))
			explanation[rcn1] .= "`n" explanation[rcn2]
	}
	return
}

;----------------------------------------------------------------------------------------

FirstSpace(text)
{
	loop, parse, text, " "
		return StrLen(a_loopfield)+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 HighlightCoversNumbers)
	{
		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
	}
}

;----------------------------------------------------------------------------------------

FoundOne(rc)
{
	global
	if (StrLen(PossibleNumbers[rc]) = 0)
	{
		WhatNext := {rc: rc, text: "no possible number"}
		return 1
	}
	else if (StrLen(PossibleNumbers[rc]) = 1)
	{
		WhatNext := {rc: rc, n: PossibleNumbers[rc], text: "one possible number"}
		return 1
	}
}

;----------------------------------------------------------------------------------------

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"
		and (cPencilMark[a_loopfield a_index] != "A" or caller = "history"))
			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")
	}
	HighlightCoversNumbers := 1
	if (number[rc] != 0)
		set(rc, number[rc],, "highlight")
	else loop, 9
		if (PencilMark[rc a_index] = 1)
			PencilMark(rc, a_index, 1)
	HighlightCoversNumbers := 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)
{
	global ValueIndex
	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)
		{
			ValueIndex := index
			return 1
		}
	}
}

;----------------------------------------------------------------------------------------

intersection(r1, r2, r3, c1, c2, c3)
{
	global
	block := ""
	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))
			block .= rc "-"
	}
	block := SubStr(block, 1, -1)
	return block
}

;----------------------------------------------------------------------------------------

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(ByRef PreviousLoopfield)
{
	numbers := ""
	cells := ""
	for i, text in PreviousLoopfield
	{
		number := SubStr(text, 2, FirstSpace(text)-2)  ; The first character is `n.
		cell := SubStr(text, FirstSpace(text)+13, 7)
		if not InStr(numbers, number)
			numbers .= number " and "
		if not InStr(cells, cell)
			cells .= cell " and "
	}
	numbers := SubStr(numbers, 1, -5)
	cells := SubStr(cells, 1, -5)
	because := SubStr(PreviousLoopfield[1], FirstSpace(PreviousLoopfield[1])+20)
	PreviousLoopfield := ""
	return numbers " can't be in " cells because
}

;----------------------------------------------------------------------------------------

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
}

;----------------------------------------------------------------------------------------

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
}

;----------------------------------------------------------------------------------------

OtherNumber(PossibleNumbers, push)
{
	loop, parse, PossibleNumbers
		if (a_loopfield != push)
	{
		return a_loopfield
		break
	}
}

;----------------------------------------------------------------------------------------

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] != "")
				color := "black"
			else if (cPencilMark[rc p] = "default")
				color := cDefault
			else if (cPencilMark[rc p] = "A")
				color := cAutoPencilled
			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 := "", caller := "")
{
	global
	if not KeepLastAction
		LastAction := ""
	if (caller != "SizeAndPosition" and caller != "highlight")
	{
		tooltip
		gui, explain:destroy
	}
	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],, "SizeAndPosition")
		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
}

;----------------------------------------------------------------------------------------

split(color, i := "")
{
	global
	if InKeys(ColorValue, color)
		color := ColorValue[color]
	if (SubStr(color, 1, 2) = "0x")			; omit "0x"
		color := SubStr(color, 3)
	loop, % 6-StrLen(color)					; add leading zeros
		color := "0" . color
	red%i% := "0x" . SubStr(color, 1, 2)	; split into red, green, blue
	green%i% := "0x" . SubStr(color, 3, 2)
	blue%i% := "0x" . SubStr(color, 5, 2)
}

;----------------------------------------------------------------------------------------

StringToGui(string)
{
	local rc
	loop, parse, AllCells, -
	{
		highlight(a_loopfield, "")  ; Highlight first because it still needs number and PencilMark.
		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, ToggleEnable, 4&
		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)
		if (StrLen(a_loopfield) > 3)
			cPencilMark[rc p] := SubStr(a_loopfield, 4)
		PencilMark[rc p] := 1
	}
	if (FixedCells = "" and SubStr5 != "")
	{
		menu, SudokuMenu, rename, 4&, &Unfix
		menu, SudokuMenu, add, &Unfix, unfix
	}
	else if (FixedCells != "" and SubStr5 = "")
	{
		menu, SudokuMenu, rename, 4&, &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])
	}
}

;----------------------------------------------------------------------------------------

text(TextControl, text)
{
	global
	if (tTextControl[TextControl] != text or HighlightCoversNumbers)
	{
		guicontrol, 1:text, %TextControl%, %text%
		tTextControl[TextControl] := text
	}
}

;----------------------------------------------------------------------------------------

UpdateOldFile:
if not InStr(string, "/")
{
	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"
		}
	}
	display0 := display
	cBackground0 := cBackground
	loop, parse, string, -, `n`r
	{
		if (a_index = 1)
			display := a_loopfield
		else if (a_index = 2)
			cBackground := a_loopfield
		else if (a_index = 3)
		{
			loop, parse, a_loopfield, `,
			{
				rc := SubStr(a_loopfield, 1, 2)
				number[rc] := SubStr(a_loopfield, 3, 1)
				if (StrLen(a_loopfield) > 3)
					cNumber[rc] := SubStr(a_loopfield, 4)
			}
		}
		else if (a_index = 4)
		{
			loop, parse, a_loopfield, `,
			{
				rc := SubStr(a_loopfield, 1, 2)
				PencilMarks := SubStr(a_loopfield, 3)
				loop, parse, PencilMarks
					PencilMark[rc a_loopfield] := 1
			}
		}
	}
	string := GuiToString()
	filedelete, %Sudoku%
	fileappend, %string%, %Sudoku%
	display := display0
	cBackground := cBackground0
}
return
Last edited by pekoe on 17 Nov 2022, 12:31, edited 292 times in total.
User avatar
oldbrother
Posts: 233
Joined: 23 Oct 2013, 05:08

Re: Sudoku

27 Mar 2016, 06:41

Very nice! :thumbup: :thumbup: :thumbup:

Is there a way to input a new Sudoku?

Thanks!
pekoe
Posts: 6
Joined: 25 Mar 2016, 11:42

Re: Sudoku

27 Mar 2016, 12:19

1. Play -> Clear board
2. Set numbers/colors with the left mouse button or the number keys.
You can save Sudokus with Sudoku -> Save as... but you can't add Sudokus to the Sudoku menu.
Last edited by pekoe on 20 Feb 2017, 10:21, edited 1 time in total.
JJohnston2
Posts: 196
Joined: 24 Jun 2015, 23:38

Re: Sudoku

27 Mar 2016, 16:00

Nice. I did have one problem where clicking on a cell and then changing a pencil note for that cell, or setting the final number for that cell, would not set the cell I just clicked on but an adjacent one instead... that was happening fairly regularly.

Otherwise, very nice... looks like you put a lot of work into it.
pekoe
Posts: 6
Joined: 25 Mar 2016, 11:42

Re: Sudoku

28 Mar 2016, 10:55

Maybe you clicked on a cell, thought you had "selected" it, then moved the mouse a little and then pressed a number key? That would explain it.
You don't have to click, just move the mouse and press the number keys.

PS: There are mouse button menus now, so it works either way.
Last edited by pekoe on 20 Feb 2017, 10:27, edited 1 time in total.
JJohnston2
Posts: 196
Joined: 24 Jun 2015, 23:38

Re: Sudoku

29 Mar 2016, 01:02

Ok that works
joefiesta
Posts: 461
Joined: 24 Jan 2016, 13:54
Location: Pa., USA

Re: Sudoku

24 May 2020, 10:52

Error: Non-existent menu item (line 1731):

gui, 1:color, %cBackground2% menu, ViewMenu, Disable, &Color numbers Ctrl+C

Occurs when using VIEW --> SWITCH between numbers and colors
pekoe
Posts: 6
Joined: 25 Mar 2016, 11:42

Re: Sudoku

15 Jan 2021, 11:33

I can't reproduce the error, maybe the tabspace in the menu item name causes the problem? Anyway, I left away the disabling and enabling of the menu item, so it can't happen again. The disabling isn't necessary because the hotkey for coloring numbers already prevents the coloring of colors.
joefiesta
Posts: 461
Joined: 24 Jan 2016, 13:54
Location: Pa., USA

Re: Sudoku

15 Jan 2021, 13:33

I haven't played this Sudoku since I reported the problem. I played today, and right after the board was loaded hit "VIEW --> SWITCH between numbers and colors" and the problem did not occur. I haven't touched the program. This tells me there is some sequence of events I must go through to have the problem occur. I haven't figured out what that sequence is, however! If I do, I'll report it.
joefiesta
Posts: 461
Joined: 24 Jan 2016, 13:54
Location: Pa., USA

Re: Sudoku

16 Jan 2021, 12:33

I see you've made some changes.

I would like to suggest the following changes:

1. Add a sizing border. Even though the gui doesn't change when sized, I like the thicker, more distinct and more visible border. Visuality is important once you get my age! (One reason I wil not use Win10 is the lack of borders.... one of dozens of reasons).
After line 124 I add:
Gui, 1: +0x40000 ; 40000: sizing border

2. Make the default background color pale blue. Why have that horrible, bright BLINDING white background for numbers?
I change line 90 to:
cBackground = %cBackground2%

3. Hide the GUI (gui 1) until the board is is done being created.
I wrap the EASY and DIFFICULT subroutines with

Gui, 1: hide
...
Gui, 1: show

this is the easy fix. you might want the board to not disappear while it is being populated. I don't like seeing the solution FLASH and then disappear.

4. If SWITCH=colors, I disable the VIEWMENU "&color numbers" entry. Rather minor, but, since it doesn't do anything, why not turn it off?

The above are all very trivial coding issues. I have, however, two more difficult suggestions.

1. After the initial board is populated, the squares that are predefined should be immutable. If I accidentally change one, I've ruined the game. And, I can't imagine why I would want to purposefully change one.

2. I would like COLOR NUMBERS to work differently (or maybe a 2nd similar function added). Once I set a color, I would like ALL subsequently added squares to AUTOMATICALLY be made that color. I want to be able to see which squares I HAVE SET and which were set initially. I would actually like this to be a default behavior, with the user-added squares a default color (which, if you don't like the idea, could be BLACK!!!).

thanks.

Love your program!

Joe P.
pekoe
Posts: 6
Joined: 25 Mar 2016, 11:42

Re: Sudoku

19 Jan 2021, 04:55

Thanks for your posts.
First, I made a tiny change with a big consequence: I thought, 2 loops were enough in GetPossibleNumbers, but it didn't solve this classic Sudoku

without a bifurcation. Now, with just one more loop, it gets the solution!
I added "Fix input" in the Sudoku menu. "Generate" and the Sudoku images are automatically fixed and "Open", "Create" and "Clear board" will lift the fix.
I didn't make other changes. I like to watch a Sudoku being created, like the backside of a clock made of glass, and I have Linux and Windows XP and my Sudoku program works fine with Wine in Linux, so I don't care much about borders in Windows, and I like a simple and pure white background for numbers. I think, a pale blue "sky" background is nice for the color Sudokus, especially for the sun Sudoku.

Return to “Scripts and Functions”

Who is online

Users browsing this forum: No registered users and 25 guests