Sudoku

Post your working scripts, libraries and tools for AHK v1.1 and older
pekoe
Posts: 6
Joined: 25 Mar 2016, 11:42

Sudoku

25 Mar 2016, 14:13

Generates easy or difficult or super difficult Sudokus and creates Sudoku images.
Solves every Sudoku, finds and explains X-wings, Y-wing chains, "empty rectangles", Swordfish, Jellyfish.
You can set your own pencil marks or let the program set pencil marks.
Cells can be highlighted, numbers, pencil marks and the background can be colored, or the nine numbers can be altogether replaced with nine colors, that is, you can play Sudoku with colors instead of numbers.

Sudoku images.png
Sudoku images.png (50.42 KiB) Viewed 501 times
Sudoku explainer.png
Sudoku explainer.png (73.8 KiB) Viewed 206 times

Code: Select all

#Requires AutoHotkey v1.1.33+
; https://www.autohotkey.com/boards/viewtopic.php?t=15291

; CONTENTS:
;
; Auto-execute section:
; Basic variables and arrays
; Main GUI = Sudoku board and menu
;
; Main GUI events
; Subroutines and hotkeys for the main GUI's menu, in the menu's order
; Other hotkeys for the main GUI
; Mouse buttons
;
; Main solving subroutines:
;	fill
;	GetPossibleNumbers/Cells
;	ContinueEliminating
; Other subroutines A-Z

#NoEnv
#singleinstance off
SendMode input  ; for click
SetTitleMatchMode, 1

;========================================================================================
;	Basic variables and arrays
;========================================================================================

AllCells := ""
loop, 9  ; rows
{
	r := a_index
	loop, 9  ; columns
	{
		c := a_index
		AllCells .= "-" r c
	}
}
AllCells := LTrim(AllCells, "-")

letter := ["A", "B", "C", "D", "E", "F", "G", "H", "I"]

unit := []
loop, 27
{
	if (a_index <= 9)
		unit[a_index] := "row " a_index
	else if (a_index <= 18)
		unit[a_index] := "column " letter[a_index-9]
	else
		unit[a_index] := "block " (a_index-18)
}

UnitCells := []
loop, 9
{
	i := a_index
	UnitCells[i] := ""
	loop, 9
		UnitCells[i] .= i a_index "-"
	UnitCells[i] := SubStr(UnitCells[i], 1, -1)		; rows
	UnitCells[i+9] := ""
	loop, 9
		UnitCells[i+9] .= a_index i "-"
	UnitCells[i+9] := SubStr(UnitCells[i+9], 1, -1)	; columns
}
UnitRows := []										; blocks
UnitColumns := []
intersection(19, 1, 2, 3, 1, 2, 3)
intersection(20, 1, 2, 3, 4, 5, 6)
intersection(21, 1, 2, 3, 7, 8, 9)
intersection(22, 4, 5, 6, 1, 2, 3)
intersection(23, 4, 5, 6, 4, 5, 6)
intersection(24, 4, 5, 6, 7, 8, 9)
intersection(25, 7, 8, 9, 1, 2, 3)
intersection(26, 7, 8, 9, 4, 5, 6)
intersection(27, 7, 8, 9, 7, 8, 9)

number := {}
PencilMark := {}
block := {}
coord := {}
ConnectedCells := {}
loop, parse, AllCells, -
{
	rc := a_loopfield
	r := SubStr(rc, 1, 1)
	c := SubStr(rc, 2, 1)
	number[rc] := 0  ; 0 if the cell is empty, else the number in the cell
	loop, 9
		PencilMark[rc a_index] := 0  ; 0 if the pencil mark is not set, 1 if it is set
	For u, cells in UnitCells
		if (u >= 19 and InStr(cells, rc))
	{
		block[rc] := u
		break
	}
	coord[rc] := letter[c] r
	ConnectedCells[rc] := ""
	loop, parse, % UnitCells[r] "-" UnitCells[c+9] "-" UnitCells[block[rc]], -
		if (a_loopfield != rc and not InStr(ConnectedCells[rc], a_loopfield))
			ConnectedCells[rc] .= "-" a_loopfield
	ConnectedCells[rc] := LTrim(ConnectedCells[rc], "-")
}

;---- colors ----
colors := []
colors[1] := ["white", "0xFFFFFF"]
colors[2] := ["yellow", "0xFFFF00"]
colors[3] := ["orange", "0xFF8040"]
colors[4] := ["red", "0xFF0000"]
colors[5] := ["purple", "0x800080"]
colors[6] := ["blue", "0x0000FF"]
colors[7] := ["light blue", "0x00FFFF"]
colors[8] := ["green", "0x00A000"]
colors[9] := ["black", "0x202020"]
; Without quote marks, 0xABCDEF would be stored as decimal, that is, as 10*16**5+11*16**4+12*16**3+13*16**2+14*16+15=11259375,
; and when it is used in gui,font, only the last six digits would be used, that is, the color would be 0x259375.
ColorName := []
ColorNumber := {}
ColorValue := {}
loop, 9
{
	ColorName[a_index] := colors[a_index][1]	; ColorName[1] = white, ColorName[2] = yellow etc.
	ColorNumber[colors[a_index][1]] := a_index	; ColorNumber["white"] = 1, ColorNumber["yellow"] = 2 etc.
	ColorValue[colors[a_index][1]] := colors[a_index][2]  ; ColorValue["white"] = 0xFFFFFF, ColorValue["yellow"] = 0xFFFF00 etc.
}
;---- pale colors to highlight cells ----
HighlightValue := {red: "0xFFCCCC", green: "0xCCFFCC", blue: "0xCCCCFF", orange: "0xFFCCA0"}
;---- starting values ----
cNumber := {}
cPencilMark := {}
highlight := {}
loop, parse, AllCells, -
{
	cNumber[a_loopfield] := "default"
	loop, 9
		cPencilMark[a_loopfield a_index] := "default"
}
display := "numbers"
background("white")
cBackgroundForColors := "0xFFEECC"

;========================================================================================
;	Main GUI = Sudoku board and menu
;========================================================================================

; The Sudoku board is actually a bunch of text controls:
; - GreySquare11 to 81 completely cover the Sudoku board.
; - WhiteSquare11 to 81 are on top of the grey squares and somewhat smaller than the grey squares, so they appear as the
; white squares of the Sudoku board, and the grey squares behind them appear to be the grid.
; - number11 to 81 contain the numbers of the Sudoku: number[11] to number[81] are the numbers set in number11 to number81. 
; number11 to 81 are transparent and exactly on top of WhiteSquare11 to 81, so that numbers appear to be set in the white
; squares.
; - PencilMark111 to 819 contain the pencil marks of the Sudoku: PencilMark[111] to PencilMark[819] are the pencil marks set
; in PencilMark111 to 819. PencilMark111 to 819 are transparent and there are 9 pencil mark text controls on top of each
; white square.
; g in Webdings:
; A g in Webdings is a square, and when it completely covers its text control, it "colors" the text control.
; - Dark grey squares in the grey squares are used to make the grid more distinct. (Without Webdings, the option -background
; for the grey squares uses the standard background color rather than the one set by the gui color command, so there is still
; a grid but paler.)
; - Red or green or blue squares in the white squares are used to highlight the squares. (=> Without Webdings, there will be
; little red and green and blue g-s in the "highlighted" squares.) highlight[11] to highlight[81] are the highlighting colors
; in WhiteSquare11 to 81. Numbers and pencil marks in highlighted squares must be on top of the highlighting squares in order
; to not be covered, too!

gui, 1:-DPIScale

loop, parse, AllCells, -
{
	gui, 1:add, text, vGreySquare%a_loopfield% -background, g
	gui, 1:add, text, vWhiteSquare%a_loopfield%
	gui, 1:add, text, vnumber%a_loopfield% +center backgroundtrans
	loop, 9
		gui, 1:add, text, vPencilMark%a_loopfield%%a_index% +center backgroundtrans
}
caption123 := "1-2-3-4-5-6-7-8-9"
loop, parse, caption123, -
	gui, 1:add, text, vcaption123%a_loopfield% +center, %a_loopfield%
captionABC := "A-B-C-D-E-F-G-H-I"
loop, parse, captionABC, -
	gui, 1:add, text, vcaptionABC%a_loopfield% +center, %a_loopfield%

; Arrays for the parameters of the text controls' text:
tTextControl := {}  ; text
cTextControl := {}  ; color
sTextControl := {}  ; size
wTextControl := {}  ; weight
fTextControl := {}  ; font
; cTextControl["number" cell] is cNumber[cell] if display="numbers", or ColorName[n] if display="colors".
; If cNumber[cell] is "default", its value is cDefault, or cFixed if the number is fixed.
; cTextControl["PencilMark" cell n] is cPencilMark[cell n] if display="numbers", or ColorName[n] if display="colors".
; If cPencilMark[cell n] is "default", its value is cDefault.
; The values of cDefault and cFixed are set and changed by background().
; cTextControl is always black in highlighted cells.

menu, SudokuMenu, add, &Easy and symmetrical, EasyAndSymmetrical
menu, SudokuMenu, add, &Difficult but not symmetrical, DifficultButNotSymmetrical
menu, SudokuMenu, add, Super d&ifficult, SuperDifficult
menu, SudokuMenu, add, &Open..., open
menu, SudokuMenu, add, &Fix, fix
menu, SudokuMenu, add, &Save as..., SaveAs
menu, SudokuMenu, add  ; separator line
menu, SudokuMenu, add, hut
menu, SudokuMenu, add, tree
menu, SudokuMenu, add, autumn tree, AutumnTree
menu, SudokuMenu, add, spiral
menu, SudokuMenu, add, heart
menu, SudokuMenu, add, smiley
menu, SudokuMenu, add, star
menu, SudokuMenu, add, Christmas tree, ChristmasTree
menu, SudokuMenu, add, crown
menu, SudokuMenu, add, sun
menu, SudokuMenu, add  ; separator line
menu, SudokuMenu, add, Create image..., CreateImage

menu, ViewMenu, add, Larger	+, larger
menu, ViewMenu, add, Smaller	-, smaller
menu, ViewMenu, add, Normal size	N, NormalSize
menu, ViewMenu, add, Mix a color...	Ctrl+C, MixColor
menu, ViewMenu, add, S&witch from numbers to colors, switch

menu, PlayMenu, add, Back	page up, back
menu, PlayMenu, add, Forward	page down, forward
menu, PlayMenu, add, &All pencil marks, AllPencilMarks
menu, PlayMenu, add, &Pencil mark singles or pairs, PencilMarkSinglesOrPairs
menu, PlayMenu, add, &No pencil marks, NoPencilMarks
menu, PlayMenu, add, &Clear board, ClearBoard

menu, SolveMenu, add, Set and explain one	Ctrl+page down, SetOne
menu, SolveMenu, add, Set &all, SetAll

menu, MenuBar, add, &Sudoku, :SudokuMenu
menu, MenuBar, add, &View, :ViewMenu
menu, MenuBar, add, &Play, :PlayMenu
menu, MenuBar, add, S&olve, :SolveMenu
menu, MenuBar, add, &?, ?

gui, 1:menu, MenuBar

menu, LeftMouseMenu, add
menu, RightMouseMenu, add

loop
{
	WinTitle := "Sudoku " A_Index
	if not WinExist(WinTitle)
		break
}
GroupAdd, SudokuWindows, %WinTitle%

zoom := 1
SizeAndPosition()
gui, 1:show, w%wGui% h%wGui%, %WinTitle%  ; GuiSize is launched

today := A_MM A_DD
if (today >= 1201 and today <= 1224)
	gosub star
else if (today >= 1225 or today <= 0106)
	gosub ChristmasTree

history := []
HistoryIndex := 0
history()

AutoPencil := 0

return

;========================================================================================
;	Main GUI events
;========================================================================================

GuiSize:
wingetpos,,, winwidth, winheight, %WinTitle% ahk_class AutoHotkeyGUI
wBorder := (winwidth-a_guiwidth)/2
hTitleMenu := winheight-a_guiheight-wBorder
return

GuiClose:
gui, 1:+OwnDialogs
something := 0
loop, parse, AllCells, -
{
	if (number[a_loopfield] != 0)
		something += 1
	else loop, 9
		if (PencilMark[a_loopfield a_index] = 1)
			something += 1
}
if something > 3
{
	msgbox, 0x2003,, Do you want to save the current situation?
	IfMsgBox, Yes
		gosub SaveAs
	IfMsgBox, Cancel
		return
}
exitapp

;========================================================================================
;	Subroutines and hotkeys for the main GUI's menu, in the menu's order
;========================================================================================
	#ifwinactive ahk_group SudokuWindows ahk_class AutoHotkeyGUI

;---- generate an easy and symmetrical Sudoku -------------------------------------------

EasyAndSymmetrical:
gosub PleaseWait
gosub ClearBoard
fill("random")
RandomCells := AllCells
sort, RandomCells, random d-
AlreadyLooped := ""
loop, parse, RandomCells, -
	if not InStr(AlreadyLooped, a_loopfield)
{
	sym1 := a_loopfield
	r := SubStr(sym1, 1, 1)
	c := SubStr(sym1, 2, 1)
	sym2 := r 10-c
	sym3 := 10-r c
	sym4 := 10-r 10-c
	sym5 := c r
	sym6 := c 10-r
	sym7 := 10-c r
	sym8 := 10-c 10-r
	SymCells := sym1 "-" sym2 "-" sym3 "-" sym4 "-" sym5 "-" sym6 "-" sym7 "-" sym8
	AlreadyLooped .= "-" SymCells
	loop, 8
		number[sym%a_index%] := 0
	context := "EasyAndSymmetrical"
	fill("minimum")
	context := ""
	StillOneSolution := 0
	if (bifurcation.length() = 0)
		StillOneSolution := 1
	else if (AllValues(minimum) = AllValues(random))
	{
		context := "EasyAndSymmetrical"
		fill("maximum")
		context := ""
		if (AllValues(maximum) = AllValues(minimum))
			StillOneSolution := 1
	}
	loop, parse, SymCells, -
		if StillOneSolution
			set(a_loopfield, 0)
		else
			number[a_loopfield] := random[a_loopfield]
	loop, parse, AllCells, -
		if not InStr(SymCells, a_loopfield)
			number[a_loopfield] := reset[a_loopfield]
}
if (display = "numbers")
	loop, parse, AllCells, -
		if (number[a_loopfield] != 0)
{
	r := SubStr(a_loopfield, 1, 1)
	c := SubStr(a_loopfield, 2, 1)
	GreenAdd := abs(r-5)*48
	BlueAdd := abs(c-5)*48
	RedShade := Format("{:#X}", 255*16**4+GreenAdd*16**2+BlueAdd)
	cNumber[a_loopfield] := RedShade
	font("number" a_loopfield, RedShade, sNumber, "Ubuntu", "Arial")
}
gosub fix
history()
gui, 1:-disabled
gui, PleaseWait:destroy
return

;---- generate a difficult (but not symmetrical) Sudoku ---------------------------------

DifficultButNotSymmetrical:
gosub PleaseWait
gosub ClearBoard
fill("random")
RandomCells := AllCells
sort, RandomCells, random d-
loop, parse, RandomCells, -
{
	if a_index < 4
	{
		set(a_loopfield, 0)
		continue
	}
	NextOmit := a_loopfield
	number[NextOmit] := 0
	context := "DifficultButNotSymmetrical"
	fill("minimum")
	context := ""
	StillOneSolution := 0
	if (bifurcation.length() = 0)
		StillOneSolution := 1
	else if (AllValues(minimum) = AllValues(random))
	{
		context := "DifficultButNotSymmetrical"
		fill("maximum")
		context := ""
		if (AllValues(maximum) = AllValues(minimum))
			StillOneSolution := 1
	}
	if StillOneSolution
		set(NextOmit, 0)
	else
		number[NextOmit] := random[NextOmit]
	loop, parse, AllCells, -
		if (a_loopfield != NextOmit)
			number[a_loopfield] := reset[a_loopfield]
}
gosub fix
history()
gui, 1:-disabled
gui, PleaseWait:destroy
return

;---- generate a super difficult Sudoku -------------------------------------------------

SuperDifficult:
CancelSuperDifficult := 0
loop
{
	gosub DifficultButNotSymmetrical
	sleep 400
	SuperDifficultString := GuiToString()
	TotalLineCount := 0
	SuperDifficult := 0
	TryAndError := 0
	loop, 40
	{
		if CancelSuperDifficult
			exit
		caller := "SuperDifficult"
		gosub setone
		caller := ""
		sleep 100
		if (next["action"] = "several possible numbers")
		{
			TryAndError := 1
			break
		}
		else if not SuperDifficult
		{
			if (LineCount >= 3)
				TotalLineCount += LineCount
			if (LineCount >= 7 or TotalLineCount >= 20)
				SuperDifficult := 1  ; No break, there might still be a TryAndError.
		}
	}
	if SuperDifficult and not TryAndError
	{
			StringToGui(SuperDifficultString)
			gosub fix
			history()
			return
	}
}

;---- open ------------------------------------------------------------------------------

open:
gui, 1:+OwnDialogs
if not FileExist(a_desktop "\Sudoku")
	FileCreateDir, %a_desktop%\Sudoku
FileSelectFile, Sudoku,, %a_desktop%\Sudoku\
if (errorlevel = 0)
{
	fileread, string, %Sudoku%
	StringToGui(string)
	history()
	AutoPencil := 0
}
return

;---- fix/unfix -------------------------------------------------------------------------

fix:
FixedCells := ""
loop, parse, AllCells, -
	if (number[a_loopfield] != 0)
		FixedCells .= "-" a_loopfield
FixedCells := LTrim(FixedCells, "-")
if (FixedCells != "")
{
	loop, parse, FixedCells, -
		if (cNumber[a_loopfield] = "default")
			set(a_loopfield, number[a_loopfield])
	menu, SudokuMenu, rename, 5&, &Unfix
	menu, SudokuMenu, add, &Unfix, unfix
	history()
}
return

unfix:
if (FixedCells != "")
{
	copy := FixedCells
	FixedCells := ""  ; FixedCells influences set()
	loop, parse, copy, -
		if (cNumber[a_loopfield] = "default")
			set(a_loopfield, number[a_loopfield])
	menu, SudokuMenu, rename, 5&, &Fix
	menu, SudokuMenu, add, &Fix, fix
	history()
}
return

;---- save as ---------------------------------------------------------------------------

SaveAs:
gui, 1:+OwnDialogs
if not FileExist(a_desktop "\Sudoku")
	FileCreateDir, %a_desktop%\Sudoku
FileSelectFile, Sudoku, S16, %a_desktop%\Sudoku\
if (errorlevel = 0)
{
	string := GuiToString()
	if not InStr(SubStr(Sudoku, -4), ".")  ; if the user didn't write an extension
		Sudoku .= ".txt"
	if FileExist(Sudoku)
		filedelete, %Sudoku%
	fileappend, %string%, %Sudoku%
}
return

;---- hut -------------------------------------------------------------------------------

hut:
string := "numbers/FFFFFF/1528B5B37,2448B5B37,2618B603D,3348B5B37,3718B603D,4248B5B37,"
. "4818B603D,528A7815D,56778563A,579916946,584916946,622A7815D,6338B5B37,6498B5B37,66878563A,"
. "685A27757,723A7815D,7428A6440,769BC9B7A,778BC9B7A,786A27757,825A7815D,8468A6440,883A27757,"
. "929A7815D,9438A6440,982A27757"
StringToGui(string)
gosub fix
AutoPencil := 0
history()
return

;---- tree ------------------------------------------------------------------------------

tree:
string := "numbers/FFFFFF/137green,149green,151green,164green,223green,271green,316green,"
. "388green,418green,485green,512green,589green,625green,674green,731green,743804000,"
. "754804000,767green,842804000,855804000,941804000,956804000"
StringToGui(string)
gosub fix
AutoPencil := 0
history()
return

;---- autumn tree -----------------------------------------------------------------------

AutumnTree:
string := "numbers/FFFFFF/133FF6400,144FFB700,1597D7327,165FFB700,226FF4000,275FF6400,"
. "317FF6400,381FF4000,412FFB700,483FF6400,5117D7327,588FFB700,628FFB700,6797D7327,735FF6400,"
. "743654000,756654000,769FFB700,842654000,855654000,941654000,954654000"
StringToGui(string)
gosub fix
AutoPencil := 0
history()
return

;---- spiral ----------------------------------------------------------------------------

spiral:
string := "numbers/FFFFFF/2420024FF,257006DFF,26500B6FF,3322500FF,37900FFFF,4276E00FF,"
. "456FFB700,48400FFB7,525B700FF,543FFFF00,561FF6E00,58600FF6E,621FF00FF,662FF2500,68800FF25,"
. "733FF00B6,748FF006D,759FF0024,78224FF00,8756DFF00,967B6FF00"
StringToGui(string)
gosub fix
AutoPencil := 0
history()
return

;---- heart -----------------------------------------------------------------------------

heart:
string := "numbers/FFFFFF/226CC0000,233CC0000,277CC0000,282CC0000,311CC0000,348CC0000,"
. "364CC0000,395CC0000,414CC0000,456CC0000,492CC0000,519CC0000,591CC0000,621CC0000,688CC0000,"
. "738CC0000,776CC0000,841CC0000,862CC0000,957CC0000"
StringToGui(string)
gosub fix
AutoPencil := 0
history()
return

;---- smiley ----------------------------------------------------------------------------

smiley:
string := "numbers/FFFF77/147FF2C00,155FF2C00,161FF2C00,231FF2C00,278FF2C00,327FF2C00,346FF2C00,"
. "369FF2C00,381FF2C00,428FF2C00,484FF2C00,525FF2C00,536FF2C00,573FF2C00,587FF2C00,621FF2C00,"
. "642FF2C00,657FF2C00,664FF2C00,685FF2C00,733FF2C00,771FF2C00,845FF2C00,853FF2C00,862FF2C00"
StringToGui(string)
gosub fix
AutoPencil := 0
history()
return

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

ChristmasTree:
string := "colors/FFEECC/114,152,196,251,343,364,448,465,532,578,637,673,721,788,828,835,842,"
. "857,863,871,884,959"
StringToGui(string)
gosub fix
AutoPencil := 0
history()
return

;---- crown -----------------------------------------------------------------------------

crown:
string := "colors/FFEECC/216,254,298,312,323,345,368,384,396,413,432,476,495,514,592,618,651,"
. "694,717,791,811,828,834,842,859,866,873,885,897"
StringToGui(string)
gosub fix
AutoPencil := 0
history()
return

;---- sun -------------------------------------------------------------------------------

sun:
string := "colors/DDEEFF/136,168,193,242,263,287,351,372,411,426,435,443,482,498,552,571,644,"
. "661,683,734,762,797,823,866,912,967"
StringToGui(string)
gosub fix
AutoPencil := 0
history()
return

;---- star ------------------------------------------------------------------------------

star:
string := "numbers/70/151FFFFFF,221FFFFFF,254FFFFFF,287FFFFFF,342FFFFFF,365FFFFFF,431FFFFFF,"
. "449FFFFFF,468FFFFFF,472FFFFFF,512FFFFFF,528FFFFFF,584FFFFFF,597FFFFFF,633FFFFFF,644FFFFFF,"
. "667FFFFFF,675FFFFFF,745FFFFFF,769FFFFFF,827FFFFFF,858FFFFFF,881FFFFFF,957FFFFFF/"
StringToGui(string)
gosub fix
AutoPencil := 0
history()
return

;---- create image ----------------------------------------------------------------------

CreateImage:

; CONTENTS:
;
; Paint an image
;
; Check the image:
; 1. Less than 17 cells?
; 2. No pencil marks?
; 3. Two numbers missing completely?
; 4. Two rows or columns in the same blocks completely empty?
; 5. Less than n possible numbers in n cells in one unit?
;
; Create a Sudoku from the image:
; loop
; {
;	Fill PencilImage with pencilled (and possible) numbers.
;	Start over when the second loop tried all found images and doesn't find any more images.
;	Give up after three iterations.
;	loop
;	{
;		loop PencilImage
;		{
;			Try all pencilled (and possible) numbers in a_loopfield.
;			If the resulting image is already in AllImages, skip the number.
;			Solve the Sudoku with fill("minimum") and fill("maximum").
;			If the solutions are identical, then there is only one solution and the Sudoku is ready.
;			Otherwise add the image to AllImages and SortedImages.
;		}
;		Take the best image from SortedImages and continue the second loop.
;		If there are no SortedImages, break the second loop and start over in the first loop.
;	}
; }

if GuiCreateImage
{
	gui, CreateImage:show
	return
}
if GuiPaintImage
{
	gui, PaintImage:show
	return
}
ContinueLastCreating := 0
if FileExist(a_desktop "\Sudoku\Create image\PencilImage.txt")
{
	msgbox, 0x2104,, The last creating was canceled. Do you want to continue it?
	IfMsgBox, Yes
	{
		fileread, PencilImage, %a_desktop%\Sudoku\Create image\PencilImage.txt
		StringToGui(PencilImage)
		history()
		ContinueLastCreating := 1
		gosub PaintImageButtonCreateImage
		return
	}
}
gosub unfix
AutoPencil := 0
loop, parse, AllCells, -
	highlight(a_loopfield, "")
tooltip
context := "CreateImage_PaintImage"
gui, 1:-disabled
gui, PaintImage:font, s12
gui, PaintImage:add, text,,
(
Paint an image with pencil marks and %display%:
Set/delete numbers and pencil marks as usual. The P key sets all possible pencil marks in a cell.
The program will try to create a Sudoku from your image:
Empty cells will be left empty, preset %display% will remain unchanged, cells with pencil marks will be filled with one of the
pencil marked %display%.
For a reasonable chance to create a Sudoku from the image, there should be preset %display% or pencil marks in at least
20 cells.
)
if (display = "colors")
{
	gui, PaintImage:add, text, xs, % "1 = " ColorName[1]
	gui, PaintImage:add, text, xp+120, % "2 = " ColorName[2]
	gui, PaintImage:add, text, xp+120, % "3 = " ColorName[3]
	gui, PaintImage:add, text, xp+120, % "4 = " ColorName[4]
	gui, PaintImage:add, text, xp+120, % "5 = " ColorName[5]
	gui, PaintImage:add, text, xs, % "6 = " ColorName[6]
	gui, PaintImage:add, text, xp+120, % "7 = " ColorName[7]
	gui, PaintImage:add, text, xp+120, % "8 = " ColorName[8]
	gui, PaintImage:add, text, xp+120, % "9 = " ColorName[9]
}
gui, PaintImage:add, button, xs y+20, Create image
gui, PaintImage:add, button, x+20, Cancel
gui, PaintImage:show,, Paint image ...
GuiPaintImage := 1
ArrangeWindows("PaintImage")
return

PaintImageGuiSize:
PaintImageWidth := a_guiwidth
PaintImageHeight := a_guiheight+30  ; +30 for the title bar
return

PaintImageButtonCancel:
PaintImageGuiClose:
PaintImageGuiEscape:
gui, PaintImage:destroy
GuiPaintImage := 0
if (context = "CreateImage")
	CancelWait := 1
else
	context := ""
return

PaintImageButtonCreateImage:
HistoryIndex0 := HistoryIndex
AutoPencil := 0
gui, 1:+OwnDialogs
winactivate, %WinTitle%
context := "CreateImage"
image := ""
PresetImage := ""
PencilImage := ""
loop, parse, AllCells, -
{
	if (number[a_loopfield] != 0)
		image .= a_loopfield "-"
	else loop, 9
		if (PencilMark[a_loopfield a_index] = 1)
	{
		image .= a_loopfield "-"
		break
	}
}
image := RTrim(image, "-")
if (StrLen(image) < 50)  ;---- Less than 17 cells? ----
{
	msgbox, There have to be preset %display% or pencil marks in at least 17 cells. (The fewest %display% possible for a proper Sudoku is 17.)
	context := "CreateImage_PaintImage"
	return
}
loop, parse, image, -
	if (number[a_loopfield] != 0)
		PresetImage .= a_loopfield "-"
	else
		PencilImage .= a_loopfield "-"
PresetImage := RTrim(PresetImage, "-")
PencilImage := RTrim(PencilImage, "-")
if (PencilImage = "") ;---- No pencil marks? ----
{
	msgbox,
(
There are no pencil marks.
There have to be pencil marks so that the Sudoku creator can try different %display%.
)
	context := "CreateImage_PaintImage"
	return
}
missing := "123456789"  ; ---- Two numbers missing completely? ----
loop, parse, image, -
{
	missing := StrReplace(missing, number[a_loopfield])
	loop, 9
	{
		if (PencilMark[a_loopfield a_index] = 1)
			missing := StrReplace(missing, a_index)
	}
}
if (StrLen(missing) >= 2)
{
	missing1 := SubStr(missing, 1, 1)
	missing2 := SubStr(missing, 2, 1)
	msgbox, % "There can be no unique solution because " item(missing1) " and " item(missing2) " are missing completely and could be swapped."
	context := "CreateImage_PaintImage"
	return
}
loop, 6  ;---- Two rows or columns in the same blocks completely empty? ----
{
	i0 := a_index*3-3  ; i1 and i2 must be in the same blocks = both in unit 1, 2, 3 or 4, 5, 6 or 7, 8, 9 etc. i0 serves as starting point to find i1 and i2.
	i1 := ""
	loop, 3
	{
		i2 := i0+a_index  ; i0 = 0 => i2 = 1, 2, 3 etc.
		empty := 1
		loop, parse, % UnitCells[i2], -
			if InStr(image, a_loopfield)
		{
			empty := 0
			break
		}
		if (empty = 1)
		{
			if (i1 = "")
				i1 := i2
			else
			{
				msgbox, % "There can be no unique solution because " unit[i1] " and " unit[i2] " are completely empty and could be swapped."
				context := "CreateImage_PaintImage"
				return
			}
		}
	}
}
loop, 27  ;---- Less than n possible numbers in n cells in one unit? ----
{
	u := a_index
	PencilCellsInU := []
	loop, parse, % UnitCells[u], -
		if InStr(PencilImage, a_loopfield)
			loop, 9
				if (PencilMark[a_loopfield a_index] = 0)  ; at least one pencil mark not set
	{
		PencilCellsInU.push(a_loopfield)
		break
	}
	if (PencilCellsInU.length() < 2)
		continue  ; next unit
	patterns := [12, 123, 1234, 12345, 123456, 1234567, 12345678, 123456789]
; The patterns will be permutated: e.g. 1234 -> 1235, 1236, 1237, 1238, 1239, 1245, 1246, 1247 etc.
; The permutated patterns will be compared with PencilCellsInU, e.g. pattern 1247: are there less than four pencil marked numbers in cells 1, 2, 4, 7?
	loop, 7
		if (patterns.length()+1 > PencilCellsInU.length())	; patterns.length()+1 = length of the last (longest) pattern
			patterns.Pop()									; Longer patterns than PencilCellsInU are not needed.
	for i, pattern in patterns
	{
		loop  ; for permutations of pattern
		{
			PencilCellsInPattern := []
			PencilMarksInPattern := ""
			loop, parse, pattern
			{
				PencilCell := PencilCellsInU[a_loopfield]  ; a_loopfield, not a_index: if the pattern is 1235, a_index 4 would get cell 4 of PencilCellsInU, but a_loopfield 5 gets cell 5 of PencilCellsInU.
				PencilCellsInPattern.push(PencilCell)
				loop, 9
					if (PencilMark[PencilCell a_index] = 1 and not InStr(PencilMarksInPattern, a_index))
						PencilMarksInPattern .= a_index
			}
			if (StrLen(PencilMarksInPattern) < StrLen(pattern))
			{
				PencilCellsText := ""
				for i, PencilCell in PencilCellsInPattern
				{
					PencilCellsText .= coord[PencilCell] " and "
					highlight(PencilCell, "blue")
				}
				PencilCellsText := SubStr(PencilCellsText, 1, -5)
				if (StrLen(PencilMarksInPattern) = 1)
					PencilMarksText := " there is only one possible " SubStr(display, 1, -1)
				else
					PencilMarksText := " there are only " StrLen(PencilMarksInPattern) " possible " display
				msgbox, % "In " PencilCellsText " " PencilMarksText ", so one cell can't be filled."
				; It's always "one cell" because shorter patterns are looped first.
				context := "CreateImage_PaintImage"
				return
			}
			pattern := NextPermutation(pattern, PencilCellsInU.length())
			if (pattern = "")
				break  ; exit the loop for permutations
		}
	}
}
PencilledNumbers := {}
loop, parse, PencilImage, -
	loop, 9
		if (PencilMark[a_loopfield a_index] = 1)
			PencilledNumbers[a_loopfield] .= a_index
AllImages := []
SortedImages := []
loop, 60
	if (a_index >= 4)
		SortedImages[a_index] := []
if ContinueLastCreating
{
	fileread, AllImagesString, %a_desktop%\Sudoku\Create image\AllImages.txt
	examined := 0
	loop, parse, AllImagesString, -
	{
		AllImages.push(a_loopfield)
		examined += 1
	}
	BestDifferences := ""
	loop, 60
		if (a_index >= 4)
	{
		SortedImagesIndex := a_index
		fileread, SortedImagesString, %a_desktop%\Sudoku\Create image\SortedImages%SortedImagesIndex%.txt
		if (BestDifferences = "" and SortedImagesString != "")
			BestDifferences := SortedImagesIndex
		loop, parse, SortedImagesString, -
			SortedImages[SortedImagesIndex].push(a_loopfield)
	}
}
CancelWait := 0
;---- Create a Sudoku from the image ----
loop
{
	fill("random")
	if not AnySolution
	{
		msgbox, There is no solution.
		context := "CreateImage_PaintImage"
		HistoryIndex := HistoryIndex0
		StringToGui(history[HistoryIndex])
		return
	}
	if (a_index = 1)
	{
		gui, PaintImage:destroy
		GuiPaintImage := 0
		gui, 1:+disabled
		gui, CreateImage:font, s12
		gui, CreateImage:add, text, vCreateImageText w500 r7
		gui, CreateImage:add, button,, Cancel
		gui, CreateImage:show,, Create image ...
		GuiCreateImage := 1
		ArrangeWindows("CreateImage")
	}
	else if (a_index = 4)
	{
		guicontrol, CreateImage:text, CreateImageText, CAN'T CREATE A SUDOKU WITH A UNIQUE SOLUTION.
		context := ""
		AllImages := ""
		SortedImages := ""
		FileRemoveDir, %a_desktop%\Sudoku\Create image, 1
		return
	}
	gosub LeaveImage
	if not ContinueLastCreating
	{
		examined := 0
		BestDifferences := ""
	}
	loop
	{
		LoopIndex := a_index
		CopyArray(PencilledNumbers, "PencilledAndPossibleNumbers", 1)
		loop, parse, PencilImage, -
		{
			rc := a_loopfield
			loop, parse, % ConnectedCells[rc], -
				if (number[a_loopfield] != 0)
					PencilledAndPossibleNumbers[rc] := StrReplace(PencilledAndPossibleNumbers[rc], number[a_loopfield])
		}
		loop, parse, PencilImage, -
		{
			PencilImageIndex := a_index
			PencilImageLoopfield := a_loopfield
			StartingValue := number[PencilImageLoopfield]
			if (HighlightedLoopfield != "")
				highlight(HighlightedLoopfield, "")
			highlight(PencilImageLoopfield, "orange")
			HighlightedLoopfield := PencilImageLoopfield
			loop, parse, % PencilledAndPossibleNumbers[PencilImageLoopfield]
				if (a_loopfield != StartingValue or LoopIndex = 1 and PencilImageIndex = 1)
			{
				set(PencilImageLoopfield, a_loopfield)
				PencilImageString := ""
				loop, parse, PencilImage, -
					PencilImageString .= number[a_loopfield]
				if InValues(AllImages, PencilImageString)
					continue
				examined += 1
				fill("minimum")
				if AnySolution
				{
					fill("maximum")
					gosub LeaveImage
					differences := 0
					loop, parse, AllCells, -
						if (maximum[a_loopfield] != minimum[a_loopfield])
							differences += 1
					if (differences = 0)
					{
						gui, 1:-disabled
						guicontrol, CreateImage:text, CreateImageText,
(
Loops: %LoopIndex%
Examined images: %examined%

Ready!
You can improve the image by swapping numbers
and by coloring, see View and ? in the menu.
)
						gui, CreateImage:add, button, x+20, Save
						highlight(HighlightedLoopfield, "")
						HighlightedLoopfield := ""
						history()
						context := ""
						AllImages := ""
						SortedImages := ""
						FileRemoveDir, %a_desktop%\Sudoku\Create image, 1
						return
					}
					else
					{
						AllImages.push(PencilImageString)
						SortedImages[differences].push(PencilImageString)
						guicontrol, CreateImage:text, CreateImageText,
(
Loop %LoopIndex%
Examined images: %examined%
Cells with no unique solution
- in the last examined image: %differences%
- in the best not yet looped image: %BestDifferences%

Please wait, this can take some time ...
)
					}
				}
				if CancelWait
					gosub CancelNow
			}
			set(PencilImageLoopfield, StartingValue)
		}
		PencilImageString := ""
		loop, 60
			if (a_index >= 4 and SortedImages[a_index].length() != 0)
		{
			PencilImageString := SortedImages[a_index].pop()
			BestDifferences := a_index
			loop, parse, PencilImage, -
				set(a_loopfield, SubStr(PencilImageString, a_index, 1))
			break
		}
		if (PencilImageString = "")
		{
			HistoryIndex := HistoryIndex0
			StringToGui(history[HistoryIndex])
			guicontrol, CreateImage:text, CreateImageText, Start over ...
			sleep 2000
			break
		}
	}
}
return

CreateImageGuiSize:
CreateImageWidth := a_guiwidth
CreateImageHeight := a_guiheight+30  ; +30 for the title bar
return

CreateImageButtonCancel:
CreateImageGuiClose:
; no CreateImageGuiEscape to avoid canceling by mistake
if (context = "CreateImage")
{
	CancelWait := 1
	return
}
CancelNow:
gui, 1:-disabled
gui, CreateImage:destroy
GuiCreateImage := 0
if (HighlightedLoopfield != "")
{
	highlight(HighlightedLoopfield, "")
	HighlightedLoopfield := ""
}
if (context = "CreateImage")
{
	HistoryIndex := HistoryIndex0
	StringToGui(history[HistoryIndex])
	gosub CreateImageSave
}
context := ""
AllImages := ""
SortedImages := ""
exit

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

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

larger:
+::
NumpadAdd::
MouseGetCell()  ; returns rm and cm
gui, 1:hide
SizeAndPosition(0.1)
gui, 1:show, w%wGui% h%wGui%
if (rm != "" and cm != "")
	MouseClickCell(rm cm)
return

;---- smaller ---------------------------------------------------------------------------

smaller:
-::
NumpadSub::
MouseGetCell()  ; returns rm and cm
gui, 1:hide
SizeAndPosition(-0.1)
gui, 1:show, w%wGui% h%wGui%
if (rm != "" and cm != "")
	MouseClickCell(rm cm)
return

;---- normal size -----------------------------------------------------------------------

NormalSize:
N::
MouseGetCell()  ; returns rm and cm
gui, 1:hide
SizeAndPosition(1-zoom)
gui, 1:show, w%wGui% h%wGui%
if (rm != "" and cm != "")
	MouseClickCell(rm cm)
return

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

MixColor:
^c::
if (GuiMixColor = 1)
{
	gui, MixColor:show
	return
}
gui, MixColor:font, s12
gui, MixColor:add, Text,, Mix a color:
gui, MixColor:add, text, xm, Red:
gui, MixColor:add, slider, xp+60 vrDecimal gSlider AltSubmit range0-255
gui, MixColor:add, text, xm, Green:
gui, MixColor:add, slider, xp+60 vgDecimal gSlider AltSubmit range0-255
gui, MixColor:add, text, xm, Blue:
gui, MixColor:add, slider, xp+60 vbDecimal gSlider AltSubmit range0-255
gui, MixColor:font, s60 c000000, Webdings
gui, MixColor:add, text, xm w80 h80 vMixColor, g
gui, MixColor:font
gui, MixColor:font, s12
gui, MixColor:add, text, x+20 w140 vMixValue
Gui, MixColor:add, Radio, xm vRadios gRadios checked, Background color
Gui, MixColor:add, Radio, xm gRadios, Custom color for numbers and pencil marks
gui, MixColor:add, button, xm default, OK
gui, MixColor:add, button, x+20, Cancel
gui, MixColor:add, text, xm,
(

Apply the color to a number or pencil mark
with the C key.
If there is more than one pencil mark in the cell,
press the key repeatedly.
)
gui, MixColor:show,, Mix a color ...
GuiMixColor := 1
ArrangeWindows("MixColor")
return

MixColorGuiSize:
MixColorWidth := a_guiwidth
MixColorHeight := a_guiheight+30  ; +30 for the title bar
return

Slider:
gui, MixColor:Submit, NoHide
mix := Format("{:#X}", rDecimal*16**4+gDecimal*16**2+bDecimal)
gui, MixColor:font, s60 c%mix%, Webdings
GuiControl, font, MixColor
GuiControl, text, MixValue, %mix%
return

Radios:
gui, MixColor:Submit, NoHide
return

MixColorButtonOK:
if (Radios = 1)
{
	if (mix != cBackground)
	{
		background(mix)
		loop, parse, AllCells, -
		{
			if (number[a_loopfield] != 0)
					set(a_loopfield, number[a_loopfield])
			else loop, 9
				if (PencilMark[a_loopfield a_index] = 1)
					PencilMark(a_loopfield, a_index, 1)
		}
		history()
	}
}
else
{
	cCustom := "xyz" . mix
	; Without "xyz", cCustom would be stored as decimal, and when it is used in gui,font, only the last six digits would be used.
	; "xyz" will be removed by hex().
}
return

MixColorButtonCancel:
MixColorGuiClose:
MixColorGuiEscape:
gui, MixColor:destroy
GuiMixColor := 0
return

;---- switch from numbers to colors/from colors to numbers ------------------------------

switch:
if (display = "colors")
{
	display := "numbers"
	background(cBackgroundForNumbers)
	menu, ViewMenu, rename, 5&, S&witch from numbers to colors
}
else
{
	display := "colors"
	background(cBackgroundForColors)
	menu, ViewMenu, rename, 5&, S&witch from colors to numbers
}
loop, parse, AllCells, -
{
	if (number[a_loopfield] != 0)
		set(a_loopfield, number[a_loopfield])
	else loop, 9
		if (PencilMark[a_loopfield a_index] = 1)
			PencilMark(a_loopfield, a_index, 1)
}
history()
return

;---- back ------------------------------------------------------------------------------

back:
pgup::
if (HistoryIndex > 1)
{
	HistoryIndex -= 1
	StringToGui(history[HistoryIndex])
}
return

;---- forward ---------------------------------------------------------------------------

forward:
pgdn::
if (HistoryIndex < history.MaxIndex())
{
	HistoryIndex += 1
	StringToGui(history[HistoryIndex])
}
return

;---- all pencil marks ------------------------------------------------------------------

AllPencilMarks:
AutoPencil := "AllPossibleNumbers"
AutoPencil()
history()
return

;---- pencil mark singles or pairs ------------------------------------------------------

PencilMarkSinglesOrPairs:
AutoPencil := "SinglesOrPairs"
AutoPencil()
history()
return

;---- no pencil marks -------------------------------------------------------------------

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

;---- clear board -----------------------------------------------------------------------

ClearBoard:
string := display "/" cBackgroundFor%display%
StringToGui(string)
history()
AutoPencil := 0
return

;---- set and explain one ---------------------------------------------------------------

SetOne:
^pgdn::
tooltip
gui, explain:destroy
loop, parse, AllCells, -
	highlight(a_loopfield, "")
GetPossibleNumbers("SetOne")
gui, 1:-disabled
gui, PleaseWait:destroy
text := ""
KeepExplanationAndHighlightings := ""
loop, parse, AllCells, -
	KeepExplanationAndHighlightings .= a_loopfield number[a_loopfield] "-"
if (next["action"] = "solved")
	return
else if (next["action"] = "no possible number")
{
	rc := next["rc"]
	highlight(rc, "red")
	MouseClickCell(rc)
	text := "This cell can't be filled."
	concat := ""
	loop, 9
		concat .= explanation[rc a_index]
	if (concat != "")
		text := concat chr(8594) " " text
	if (SubStr(text, 1, 1) = chr(8594))  ; no explanations added
		text := SubStr(text, 3)
}
else if (next["action"] = "no possible cell")
{
	u := next["u"]
	n := next["n"]
	loop, parse, % UnitCells[u], -
		if (number[a_loopfield] = 0)
	{
		highlight(a_loopfield, "red")
		MouseClickCell(a_loopfield)
		sleep 400
	}
	text := n " can't be set anywhere in " unit[u] "."
	concat := ""
	loop, parse, % UnitCells[u], -
		concat .= explanation[a_loopfield n]
	if (concat != "")
		text := concat chr(8594) " " text
	if (SubStr(text, 1, 1) = chr(8594))
		text := SubStr(text, 3)
}
else if (next["action"] = "one possible number")
{
	rc := next["rc"]
	n := next["n"]
	highlight(rc, "green")				; Highlight after set would reset n and cause a flicker.
	KeepExplanationAndHighlightings .= rc n	; Set before KeepExplanationAndHighlightings would delete KeepExplanationAndHighlightings.
	set(rc, n)
	if (caller != "SuperDifficult")
		MouseClickCell(rc)
	loop, parse, % ConnectedCells[rc], -
		PencilMark(a_loopfield, n, 0)
	AutoPencil()
	history()
	text := n " is the only possible number in " rc "."
	concat := ""
	loop, 9
		if (a_index != n)
			concat .= explanation[rc a_index]
	if (concat != "")
		text := concat chr(8594) " " text
	if (SubStr(text, 1, 1) = chr(8594))
		text := SubStr(text, 3)
}
else if (next["action"] = "one possible cell")
{
	u := next["u"]
	rc := next["rc"]
	n := next["n"]
	highlight(rc, "green")
	KeepExplanationAndHighlightings .= rc n
	set(rc, n)
	if (caller != "SuperDifficult")
		MouseClickCell(rc)
	loop, parse, % ConnectedCells[rc], -
		PencilMark(a_loopfield, n, 0)
	AutoPencil()
	history()
	text := rc " is the only possible cell for " n " in " unit[u] "."
	concat := ""
	loop, parse, % UnitCells[u], -
		if (a_loopfield != rc)
			concat .= explanation[a_loopfield n]
	if (concat != "")
		text := concat chr(8594) " " text
	if (SubStr(text, 1, 1) = chr(8594))
		text := SubStr(text, 3)
}
else if (next["action"] = "several possible numbers")
{
	if (caller = "SuperDifficult")
		return
	rc := next["rc"]
	SeveralPossibleNumbers := next["n"]
	MouseClickCell(rc)
	ProvedNumbers := ""
	loop, parse, SeveralPossibleNumbers
	{
		set(rc, a_loopfield)
		fill("minimum")
		if AnySolution
			ProvedNumbers .= a_loopfield
		history()
		gosub back
	}
	if (StrLen(ProvedNumbers) = 0)
	{
		highlight(rc, "red")
		text := "This cell could be filled with "
		loop, parse, SeveralPossibleNumbers
			text .= a_loopfield " or "
		text := SubStr(text, 1, -4)
		text .= ", but there is no solution with any of them."
	}
	else
	{
		if (StrLen(ProvedNumbers) = 1)
		{
			highlight(rc, "green")
			KeepExplanationAndHighlightings .= rc ProvedNumbers
			set(rc, ProvedNumbers)
			loop, parse, % ConnectedCells[rc], -
				PencilMark(a_loopfield, ProvedNumbers, 0)
			AutoPencil()
			history()
		}
		else
			highlight(rc, "blue")
		text := "Possible numbers: "
		loop, parse, SeveralPossibleNumbers
			text .= a_loopfield ", "
		text := SubStr(text, 1, -2) ". There is a solution with: "
		loop, parse, ProvedNumbers
			text .= a_loopfield ", "
		text := SubStr(text, 1, -2) "."
	}
}
TextElements := []
loop, parse, text, $
	TextElements.push(a_loopfield)
;---- omit repetitions ----
TextElements2 := []
for i, element in TextElements
	if not InValues(TextElements2, element)
		TextElements2.push(element)
CopyArray(TextElements2, "TextElements", 1)
;--------
TextElements := merge(TextElements, "n")
TextElements := merge(TextElements, "rc")
;---- rc -> coord[rc], n -> ColorName[n], "number" -> "color" ----
text := ""
for i, element in TextElements
	text .= element "`n"
text := SubStr(text, 1, -1)
text2 := ""
loopfield1 := ""
loopfield2 := ""
delimiter1 := ""
delimiter2 := ""
loop, parse, text
{
	if (a_loopfield = a_space
	or a_loopfield = "/"
	or a_loopfield = "."
	or a_loopfield = ":"
	or a_loopfield = ","
	or a_loopfield = ";"
	or a_loopfield = "`n"
	or a_loopfield = "`r")
	{
		delimiter1 := delimiter2
		delimiter2 := a_loopfield
		if (loopfield2 = "")
			text2 .= delimiter2
		else if loopfield2 is digit
		{
			if (StrLen(loopfield2) = 1)
			{
				if (display = "colors")
				{
					if (delimiter1 = "" or delimiter1 = "`n" or delimiter1 = "`r" or delimiter1 = " " and loopfield1 = chr(8594))
						text2 .= format("{:T}", ColorName[loopfield2]) delimiter2
					else if (loopfield1 != "row" and loopfield1 != "column" and loopfield1 != "block")
						text2 .= ColorName[loopfield2] delimiter2
					else
						text2 .= loopfield2 delimiter2
				}
				else
					text2 .= loopfield2 delimiter2
			}
			else if (StrLen(loopfield2) = 2)
				text2 .= coord[loopfield2] delimiter2
		}
		else
			text2 .= loopfield2 delimiter2
		loopfield1 := loopfield2
		loopfield2 := ""
	}
	else
		loopfield2 .= a_loopfield
}
if (display = "colors")
	text2 := StrReplace(text2, "number", "color")
text := text2
;---- highlight cells ----
loop, parse, AllCells, -
	if (highlight[a_loopfield] != "green")
		if InStr(text, coord[a_loopfield] " <red>")
			highlight(a_loopfield, "red")
		else if InStr(text, coord[a_loopfield] " <blue>")
			highlight(a_loopfield, "blue")
text := StrReplace(text, " <red>")
text := StrReplace(text, " <blue>")
;---- wrap text ----
text2 := ""
LineLenMax := 120
loop, parse, text, `n`r
	if (a_loopfield != "")
{
	line := a_loopfield
	if (StrLen(line) > LineLenMax)
	{
		line2 := ""
		Line2Len := 0
		loop, parse, line, %a_space%
			if (a_loopfield != "")
		{
			Line2Len += StrLen(a_loopfield)
			if (Line2Len > LineLenMax)
			{
				line2 .= "`n" a_loopfield
				Line2Len := 0
			}
			else
				line2 .= " " a_loopfield
		}
		line := LTrim(line2)
	}
	text2 .= "`n" line
}
text := SubStr(text2, 2)
;---- tooltip or gui? ----
loop, parse, text, `n`r
	LineCount := a_index
if (caller != "SuperDifficult")
{
	if (LineCount <= 4)
		tooltip, %text%
	else
	{
		gui, explain:-caption +border
		gui, explain:color, FFFFE1
		gui, explain:font, s12
		gui, explain:add, text,, %text%
		gui, explain:show
		ArrangeWindows("explain")
	}
	sleep 100
	gui, 1:show
	MouseClickCell(rm cm)
}
return

ExplainGuiSize:
ExplainWidth := a_guiwidth
ExplainHeight := a_guiheight
return

ExplainGuiEscape:
gui, explain:destroy
loop, parse, AllCells, -
	highlight(a_loopfield, "")
return

;---- set all ---------------------------------------------------------------------------

SetAll:
gui, 1:+OwnDialogs
loop, parse, AllCells, -
	highlight(a_loopfield, "")
fill("minimum")
if not AnySolution
	msgbox, There is no solution!
else if AnySolution
{
	history()
	sleep 1000
	fill("maximum")
	if (AllValues(maximum) = AllValues(minimum))
		msgbox, There is only one solution!
	else
	{
		history()
		msgbox, There is a second solution!
	}
}
return

;---- ? ---------------------------------------------------------------------------------

?:
if (GuiHelp = 1)
{
	gui, help:show
	return
}
gui, help:font, s12
gui, help:add, text,,
(
Set numbers with the left mouse button or the number keys.
Delete numbers with the left mouse button.
Swap numbers with the S key.
Set/delete pencil marks with the right mouse button or with Shift+number key.
Set all possible pencil marks in a cell with the P key.
Delete everything in a cell with the space bar.
Color the just set number/pencil mark or (three seconds later) any number/pencil mark with the R, G, B, D or C key.
If there is more than one pencil mark in a cell, press the key repeatedly.
R, G, B, D, C = red, green, blue, default color, custom color (see "Mix a color..." in the View menu).
Colors can be used to highlight numbers and pencil marks or to improve a Sudoku image.
"All pencil marks" and "Pencil mark singles or pairs" in the Play menu highlight one or two possible cells in a row/column/block
with red/green/blue pencil marks.
"Switch from numbers to colors" in the View menu is something different:
nine colors REPLACE the nine numbers, and the Sudoku must be solved with colors instead of numbers.
)
gui, help:add, text, xs, % "1 = " ColorName[1]
gui, help:add, text, xp+120, % "2 = " ColorName[2]
gui, help:add, text, xp+120, % "3 = " ColorName[3]
gui, help:add, text, xp+120, % "4 = " ColorName[4]
gui, help:add, text, xp+120, % "5 = " ColorName[5]
gui, help:add, text, xs, % "6 = " ColorName[6]
gui, help:add, text, xp+120, % "7 = " ColorName[7]
gui, help:add, text, xp+120, % "8 = " ColorName[8]
gui, help:add, text, xp+120, % "9 = " ColorName[9]
gui, help:add, text, xs y+10,
(
Highlight a cell/delete the highlighting with Shift/Ctrl/Alt+left click.
Highlight all cells with the same number/delete the highlightings with Shift/Ctrl/Alt+right click on one of the cells.
The arrow keys also move the mouse cursor.
The Esc key deletes the tooltip and all highlightings and cancels "Super difficult".
See the menus for more hotkeys.

The compiled program (that is, the .exe file but not the original .ahk file) also works with Wine in Linux,
but without the font Webdings colors will appear as "=", and highlighted cells will appear as "g".
To fix this, install ttf-mscorefonts-installer or copy Webdings.ttf from the Windows fonts folder (C:\Windows\Fonts)
to the Linux truetype folder (probably /usr/share/fonts/truetype).
Open the Linux fonts folder "as Administrator"/"as Root"!
)
gui, help:show,, ?
GuiHelp := 1
ArrangeWindows("help")
return

HelpGuiSize:
HelpWidth := a_guiwidth
HelpHeight := a_guiheight+30  ; +30 for the title bar
return

HelpGuiClose:
HelpGuiEscape:
gui, help:destroy
GuiHelp := 0
return

;========================================================================================
;	Other hotkeys for the main GUI
;========================================================================================

~Esc::  ; cancel
tooltip
gui, explain:destroy
loop, parse, AllCells, -
	highlight(a_loopfield, "")
swap1 := ""
CancelSuperDifficult := 1
return

space::  ; delete everything in a cell
if (MouseGetCell() and not InStr(FixedCells, rm cm))
{
	set(rm cm, 0, "default")
	loop, 9
		PencilMark(rm cm, a_index, 0, "default")
	AutoPencil()
	history()
}
return

;---- hotkey letters --------------------------------------------------------------------

r::color("red")
g::color("green")
b::color("blue")
d::color("default")
c::
if (cCustom != "")
	color(cCustom)
return

p::  ; set all pencil marks
if MouseGetCell() and not InStr(FixedCells, rm cm)
{
	set(rm cm, 0)
	loop, 9
	{
		i := a_index
		valid := 1
		loop, parse, % ConnectedCells[rm cm], -
			if (number[a_loopfield] = i)
			{
				valid := 0
				break
			}
		if valid
			PencilMark(rm cm, i, 1)
	}
	history()
}
return

s::  ; swap numbers
if MouseGetCell() and not InStr(FixedCells, rm cm)
{
	if (number[rm cm] != 0 and swap1 = "")
	{
		swap1 := number[rm cm]
		tooltip, % "Swap " item(swap1) " and ..."
	}
	else if (number[rm cm] != 0 and number[rm cm] = swap1)
	{
		swap1 := ""
		tooltip
	}
	else if (number[rm cm] != 0 and swap1 != "" and number[rm cm] != swap1)
	{
		swap2 := number[rm cm]
		loop, parse, AllCells, -
		{
			if (number[a_loopfield] = swap1)
				set(a_loopfield, swap2)
			else if (number[a_loopfield] = swap2)
				set(a_loopfield, swap1)
		}
		history()
		swap1 := ""
		swap2 := ""
		tooltip
	}
}
return

;---- set numbers -----------------------------------------------------------------------

1::
2::
3::
4::
5::
6::
7::
8::
9::
numpad1::
numpad2::
numpad3::
numpad4::
numpad5::
numpad6::
numpad7::
numpad8::
numpad9::
if (NextInputWait != 1 and MouseGetCell())
{
	NextInputWait := 1
	n := SubStr(a_thislabel, 0)
	if not InStr(FixedCells, rm cm)
	{
		valid := 1
		loop, parse, % ConnectedCells[rm cm], -
			if (number[a_loopfield] = n)
			{
				valid := 0
				break
			}
		if valid
		{
			set(rm cm, n, "default")
			loop, parse, % ConnectedCells[rm cm], -
				PencilMark(a_loopfield, n, 0)
			AutoPencil()
			LastAction := {action: "number", cell: rm cm, n: n, c: "", time: A_TickCount}
			history()
		}
		else
		{
			set(rm cm, n)
			history()
			sleep 200
			gosub back
			history.pop()
		}
	}
	NextInputWait := 0
}
return

;---- set/delete pencil marks -----------------------------------------------------------

+1::  ; set/delete a single pencil mark
+2::
+3::
+4::
+5::
+6::
+7::
+8::
+9::
+numpad1::
+numpad2::
+numpad3::
+numpad4::
+numpad5::
+numpad6::
+numpad7::
+numpad8::
+numpad9::
if (NextInputWait != 1 and MouseGetCell())
{
	NextInputWait := 1
	n := SubStr(a_thislabel, 0)
	if (number[rm cm] = 0)
	{
		if (PencilMark[rm cm n] = 0)
		{
			valid := 1
			loop, parse, % ConnectedCells[rm cm], -
				if (number[a_loopfield] = n)
				{
					valid := 0
					break
				}
			if valid
			{
				PencilMark(rm cm, n, 1)
				LastAction := {action: "PencilMark", cell: rm cm, n: n, c: "", time: A_TickCount}
				history()
			}
			else
			{
				PencilMark(rm cm, n, 1)
				sleep 200
				PencilMark(rm cm, n, 0)
			}
		}
		else
		{
			PencilMark(rm cm, n, 0, "default")
			history()
		}
	}
	NextInputWait := 0
}
return

;---- move the mouse cursor -------------------------------------------------------------

~left::
~right::
~up::
~down::
key := LTrim(a_thishotkey, "~")
if (key = "left")
{
	hor := -1
	ver := 0
}
else if (key = "right")
{
	hor := 1
	ver := 0
}
else if (key = "up")
{
	hor := 0
	ver := -1
}
else if (key = "down")
{
	hor := 0
	ver := 1
}
if MouseGetCell()
{
	if (cm + hor >= 1 and cm + hor <= 9)
		cm += hor
	if (rm + ver >= 1 and rm + ver <= 9)
		rm += ver
}
else
{
	if (cm = "")
		cm := xm<=wBorder ? 1 : 9
	if (rm = "")
		rm := ym<=hTitleMenu ? 1 : 9
}
MouseClickCell(rm cm)
return

;========================================================================================
;	Mouse buttons
;========================================================================================

~lbutton::
if MouseGetCell() and not InStr(FixedCells, rm cm)
{
	menu, LeftMouseMenu, deleteall
	loop, 9
	{
		n := a_index
		valid := 1
		loop, parse, % ConnectedCells[rm cm], -
			if (number[a_loopfield] = n)
		{
			valid := 0
			break
		}
		if valid
		{
			if (display = "colors")
				item := ColorName[n]
			else
				item := n
			menu, LeftMouseMenu, add, %item%, SetDeleteNumber
			if (number[rm cm] = n)
				menu, LeftMouseMenu, check, %item%
		}
	}
	menu, LeftMouseMenu, show
}
return

SetDeleteNumber:
if (display = "numbers")
	n := a_thismenuitem
else
	n := ColorNumber[a_thismenuitem]
if (number[rm cm] != n)
{
	set(rm cm, n)
	loop, parse, % ConnectedCells[rm cm], -
		PencilMark(a_loopfield, n, 0)
	AutoPencil()
	LastAction := {action: "number", cell: rm cm, n: n, c: "", time: A_TickCount}
}
else
{
	set(rm cm, 0, "default")
	AutoPencil()
}
history()
return

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

~rbutton::
if (MouseGetCell() and number[rm cm] = 0)
{
	menu, RightMouseMenu, deleteall
	loop, 9
	{
		n := a_index
		valid := 1
		loop, parse, % ConnectedCells[rm cm], -
			if (number[a_loopfield] = n)
		{
			valid := 0
			break
		}
		if valid
		{
			if (display = "colors")
				item := "pencil mark " ColorName[a_index]
			else
				item := "pencil mark " a_index
			menu, RightMouseMenu, add, %item%, SetDeletePencilMark
			if (PencilMark[rm cm a_index] = 1)
				menu, RightMouseMenu, check, %item%
		}
	}
	menu, RightMouseMenu, show
}
return

SetDeletePencilMark:
if (display = "numbers")
	n := SubStr(a_thismenuitem, 13)
else
	n := ColorNumber[SubStr(a_thismenuitem, 13)]
if (PencilMark[rm cm n] = 0)
{
	PencilMark(rm cm, n, 1)
	LastAction := {action: "PencilMark", cell: rm cm, n: n, c: "", time: A_TickCount}
}
else
	PencilMark(rm cm, n, 0, "default")
history()
return

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

+lbutton::UserHighlight("red")
^lbutton::UserHighlight("green")
!lbutton::UserHighlight("blue")

+rbutton::UserHighlightAllOfOne("red")
^rbutton::UserHighlightAllOfOne("green")
!rbutton::UserHighlightAllOfOne("blue")

UserHighlight(color)
{
	global
	if MouseGetCell()
	{
		if (highlight[rm cm] != color)
			highlight(rm cm, color)
		else
			highlight(rm cm, "")
	}
}

UserHighlightAllOfOne(color)
{
	global
	if (MouseGetCell() and number[rm cm] != 0)
	{
		AllHighlighted := 1
		loop, parse, AllCells, -
			if (number[a_loopfield] = number[rm cm] and highlight[a_loopfield] != color)
				AllHighlighted := 0
		if not AllHighlighted
		{
			loop, parse, AllCells, -
				if (number[a_loopfield] = number[rm cm])
					highlight(a_loopfield, color)
				else if (highlight[a_loopfield] = color)
					highlight(a_loopfield, "")
		}
		else
		{
			loop, parse, AllCells, -
				if (highlight[a_loopfield] = color)
					highlight(a_loopfield, "")
		}
	}
}

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

fill(FillWith)
{
	local rc, n
	%FillWith% := {}
	bifurcation := []
	if (FillWith = "minimum")
	{
		reset := {}
		loop, parse, AllCells, -
			reset[a_loopfield] := number[a_loopfield]
	}
	else if (FillWith = "maximum")
	{
		loop, parse, AllCells, -
			set(a_loopfield, reset[a_loopfield])
	}
	loop
	{
		GetPossibleNumbers()
		if (next["action"] = "solved")
		{
			loop, parse, AllCells, -
				%FillWith%[a_loopfield] := number[a_loopfield]
			AnySolution := 1
			return
		}
		if (next["action"] = "no possible number" or next["action"] = "no possible cell")
		{
			if (bifurcation.length() = 0)
			{
				AnySolution := 0
				return
			}
			LastBifurcation := bifurcation.pop()
			loop, parse, AllCells, -
				set(a_loopfield, SubStr(LastBifurcation["string"], a_index, 1))
			rc := LastBifurcation["rc"]
			if (FillWith = "minimum")
				n := SubStr(LastBifurcation["PossibleNumbers"], 1, 1)
			else if (FillWith = "maximum")
				n := SubStr(LastBifurcation["PossibleNumbers"], 0)
			else if (FillWith = "random")
			{
				n_random := ""
				loop, parse, % LastBifurcation["PossibleNumbers"]
					n_random .= a_loopfield "-"
				sort, n_random, random d-
				n := SubStr(n_random, 1, 1)
			}
			set(rc, n)
			if (StrReplace(LastBifurcation["PossibleNumbers"], n) != "")
				bifurcation.push({string: LastBifurcation["string"], rc: LastBifurcation["rc"], PossibleNumbers: StrReplace(LastBifurcation["PossibleNumbers"], n)})
		}
		else if (next["action"] = "one possible number" or next["action"] = "one possible cell")
		{
			rc := next["rc"]
			n := next["n"]
			set(rc, n)
		}
		else if (next["action"] = "several possible numbers")
		{
			rc := next["rc"]
			if (FillWith = "minimum")
				n := SubStr(next["n"], 1, 1)
			else if (FillWith = "maximum")
				n := SubStr(next["n"], 0)
			else if (FillWith = "random")
			{
				n_random := ""
				loop, parse, % next["n"]
					n_random .= a_loopfield "-"
				sort, n_random, random d-
				n := SubStr(n_random, 1, 1)
			}
			set(rc, n)
			string := ""
			loop, parse, AllCells, -
				string .= number[a_loopfield]
			bifurcation.push({string: string, rc: rc, PossibleNumbers: StrReplace(next["n"], n)})
		}
		if (FillWith = "minimum")
		{
			if (context = "EasyAndSymmetrical" and bifurcation.length() = 0
			and number[sym1] != 0
			and number[sym2] != 0
			and number[sym3] != 0
			and number[sym4] != 0
			and number[sym5] != 0
			and number[sym6] != 0
			and number[sym7] != 0
			and number[sym8] != 0)
				return
			if (context = "DifficultButNotSymmetrical" and bifurcation.length() = 0 and number[NextOmit] != 0)
				return
		}
	}
}

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

GetAllPossibleNumbers()
{
	local rc
	AllPossibleNumbers := {}
	loop, parse, AllCells, -
		if (number[a_loopfield] = 0)
	{
		rc := a_loopfield
		AllPossibleNumbers[rc] := "123456789"
		loop, parse, % ConnectedCells[rc], -
			if (number[a_loopfield] != 0)
				AllPossibleNumbers[rc] := StrReplace(AllPossibleNumbers[rc], number[a_loopfield])
	}
}

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

GetPossibleCells(all := "")
{
	local u, n
	%all%PossibleCells := {}
	loop, 27
	{
		u := a_index
		AlreadySet := ""
		loop, parse, % UnitCells[u], -
			if (number[a_loopfield] != 0)
				AlreadySet .= number[a_loopfield]
		loop, 9
			if not InStr(AlreadySet, a_index)
		{
			n := a_index
			%all%PossibleCells[u n] := []
			loop, parse, % UnitCells[u], -
				if (number[a_loopfield] = 0 and InStr(%all%PossibleNumbers[a_loopfield], n))
					%all%PossibleCells[u n].push(a_loopfield)
		}
	}
}

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

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

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

	solved := 1
	loop, parse, AllCells, -
		if (number[a_loopfield] = 0)
	{
		solved := 0
		break
	}
	if solved
	{
		next := {action: "solved"}
		return
	}
	explanation := {}  ; explanation[rc n] will be a text string, explaining why n can't be set in cell rc.
	GetAllPossibleNumbers()
	CopyArray(AllPossibleNumbers, "PossibleNumbers", 1)
	if (context = "CreateImage")
		loop, parse, PencilImage, -
			if (number[a_loopfield] = 0)
				loop, 9
					if not InStr(PencilledNumbers[a_loopfield], a_index)
						PossibleNumbers[a_loopfield] := StrReplace(PossibleNumbers[a_loopfield], a_index)
	loop, parse, AllCells, -
		if (number[a_loopfield] = 0)
	{
		if (StrLen(PossibleNumbers[a_loopfield]) = 0)
		{
			next := {rc: a_loopfield, action: "no possible number"}
			return
		}
		else if (StrLen(PossibleNumbers[a_loopfield]) = 1)
		{
			next := {rc: a_loopfield, n: PossibleNumbers[a_loopfield], action: "one possible number"}
			return
		}
	}
	GetPossibleCells()
	for un, cells in PossibleCells
	{
		u := SubStr(un, 1, -1)
		n := SubStr(un, 0)
		if (PossibleCells[u n].length() = 0)
		{
			next := {u: u, n: n, action: "no possible cell"}
			return
		}
		else if (PossibleCells[u n].length() = 1)
		{
			rc := PossibleCells[u n][1]
			next := {u: u, rc: rc, n: n, action: "one possible cell"}
			return
		}
	}
	EmptyCells := {}
	loop, 27
	{
		u := a_index
		EmptyCells[u] := 0
		loop, parse, % UnitCells[u], -
			if (number[a_loopfield] = 0)
				EmptyCells[u] += 1
	}
	loop, 9
	{
		main_index := a_index
		if (caller = "SetOne" and main_index = 2)
			gosub PleaseWait
		CellsWith2PossibleNumbers := ""
		loop, parse, AllCells, -
			if (number[a_loopfield] = 0 and StrLen(PossibleNumbers[a_loopfield]) = 2)
				CellsWith2PossibleNumbers .= a_loopfield "-"
		CellsWith2PossibleNumbers := RTrim(CellsWith2PossibleNumbers, "-")
		;---- two cells with the same two possible numbers ----
		loop, parse, CellsWith2PossibleNumbers, -
		{
			cell1 := a_loopfield
			i := a_index
			loop, parse, CellsWith2PossibleNumbers, -
				if (a_index > i and InStr(ConnectedCells[cell1], a_loopfield))
			{
				cell2 := a_loopfield
				if (PossibleNumbers[cell1] = PossibleNumbers[cell2])
				{
					n1 := SubStr(PossibleNumbers[cell1], 1, 1)
					n2 := SubStr(PossibleNumbers[cell1], 2, 1)
					loop, parse, % ConnectedCells[cell1], -
						if (InStr(ConnectedCells[cell2], a_loopfield) and number[a_loopfield] = 0)
							for i, n in [n1, n2]
								if InStr(PossibleNumbers[a_loopfield], n)
					{
						rc := a_loopfield
						if (caller = "SetOne")
						{
							explanation[rc n] := n " can't be in " rc " <red> because "
							. n1 " and " n2 " are the only possible numbers in "
							. cell1 " <blue> and " cell2 " <blue>, so they must be there.$"
							concat := ""
							loop, parse, % cell1 "-" cell2, -
								loop, 9
									if (a_index != n1 and a_index != n2)  ; no other possible numbers
										ExplConcat(concat, explanation[a_loopfield a_index])
							if (concat != "")
								explanation[rc n] := concat explanation[rc n]
						}
						if (eliminate(rc, n) = "one left")
							return
					}
					break
				}
			}
		}
		;---- two numbers with the same two possible cells ----
		for un1, cells1 in PossibleCells
		{
			u1 := SubStr(un1, 1, -1)
			n1 := SubStr(un1, 0)
			if (PossibleCells[u1 n1].length() = 2 and EmptyCells[u1] > 2)
				for un2, cells2 in PossibleCells
			{
				u2 := SubStr(un2, 1, -1)
				n2 := SubStr(un2, 0)
				if (n2 != n1
				and PossibleCells[u2 n2].length() = 2
				and EmptyCells[u2] > 2
				and PossibleCells[u2 n2][1] = PossibleCells[u1 n1][1]
				and PossibleCells[u2 n2][2] = PossibleCells[u1 n1][2])
				{
					for i, cell in PossibleCells[u1 n1]
						loop, parse, % PossibleNumbers[cell]
							if (a_loopfield != n1 and a_loopfield != n2)
					{
						n3 := a_loopfield
						if (caller = "SetOne")
						{
							rc1 := PossibleCells[u1 n1][1]
							rc2 := PossibleCells[u1 n1][2]
							if (u2 = u1)
								explanation[cell n3] := "In " unit[u1] ", " n1 " and " n2
							else
								explanation[cell n3] := n1 " in " unit[u1] " and " n2 " in " unit[u2]
							explanation[cell n3] .= " can only be in " rc1 " <blue> and " rc2 " <blue>, so they will be there, and other numbers can't be there.$"
							concat := ""
							loop, parse, % UnitCells[u1], -
								if (a_loopfield != rc1 and a_loopfield != rc2)
									ExplConcat(concat, explanation[a_loopfield n1])
							loop, parse, % UnitCells[u2], -
								if (a_loopfield != rc1 and a_loopfield != rc2)
									ExplConcat(concat, explanation[a_loopfield n2])
							if (concat != "")
								explanation[cell n3] := concat explanation[cell n3]
						}
						if (eliminate(cell, n3) = "one left")
							return
					}
					break
				}
			}
		}
		;---- three cells with together three possible numbers ----
		if (caller = "SetOne")
			loop, 27
		{
			u := a_index
			loop, parse, % UnitCells[u], -
				if (number[a_loopfield] = 0 and StrLen(PossibleNumbers[a_loopfield]) <= 3)
			{
				cell1 := a_loopfield
				i := a_index
				loop, parse, % UnitCells[u], -
					if (a_index > i and number[a_loopfield] = 0 and PossibleNumbersLen(cell1, a_loopfield) <= 3)
				{
					cell2 := a_loopfield
					i := a_index
					loop, parse, % UnitCells[u], -
						if (a_index > i and number[a_loopfield] = 0 and PossibleNumbersLen(cell1, cell2, a_loopfield) = 3)
					{
						cell3 := a_loopfield
						loop, parse, % UnitCells[u], -
							if (a_loopfield != cell1 and a_loopfield != cell2 and a_loopfield != cell3 and number[a_loopfield] = 0)
								for i, n in [n1, n2, n3]
									if InStr(PossibleNumbers[a_loopfield], n)
						{
							rc := a_loopfield
							if (caller = "SetOne")
							{
								explanation[rc n] := n " can't be in " rc " <red> because "
								. n1 " and " n2 " and " n3 " are the only possible numbers in "
								. cell1 " <blue> and " cell2 " <blue> and " cell3 " <blue>"
								. ", so they must be there.$"
								concat := ""
								loop, parse, % cell1 "-" cell2 "-" cell3, -
									loop, 9
										if (a_index != n1 and a_index != n2 and a_index != n3)  ; no other possible numbers
											ExplConcat(concat, explanation[a_loopfield a_index])
								if (concat != "")
									explanation[rc n] := concat explanation[rc n]
							}
							if (eliminate(rc, n) = "one left")
								return
						}
					}
				}
			}
		}
		;---- three numbers with together three possible cells ----
		if (caller = "SetOne" and main_index > 1)
			for un, cells1 in PossibleCells
		{
			u := SubStr(un, 1, -1)
			n1 := SubStr(un, 0)
			if (cells1.length() <= 3 and EmptyCells[u] > 3)
			loop, 9
			{
				n2 := a_index
				if (n2 != n1 and (PossibleCells[u n2].length() = 2 or PossibleCells[u n2].length() = 3))
				{
					cells2 := ArrayAdd(cells1, PossibleCells[u n2])
					if (cells2.length() = 3)
						loop, 9
					{
						n3 := a_index
						if (n3 != n1 and n3 != n2 and (PossibleCells[u n3].length() = 2 or PossibleCells[u n3].length() = 3))
						{
							cells3 := ArrayAdd(cells2, PossibleCells[u n3])
							if (cells3.length() = 3)
								for i, cell in cells3
									loop, parse, % PossibleNumbers[cell]
										if (a_loopfield != n1 and a_loopfield != n2 and a_loopfield != n3)
							{
								n4 := a_loopfield
								if (caller = "SetOne")
								{
									CellsString := ""
									for i2, cell2 in cells3
										CellsString .= cell2 "-"
									sort, CellsString, d-
									rc1 := SubStr(CellsString, 1, 2)
									rc2 := SubStr(CellsString, 4, 2)
									rc3 := SubStr(CellsString, 7, 2)
									explanation[cell n4] := "In " unit[u] ", " n1 " and " n2 " and " n3 " must be in " rc1 " <blue> and " rc2
									. " <blue> and " rc3 " <blue>. That's three cells for three numbers, so there can't be other numbers in these cells.$"
									concat := ""
									loop, parse, % UnitCells[u], -
										if (not InValues(cells3, a_loopfield) and number[a_loopfield] = 0)  ; no other possible cells
											loop, 3
												ExplConcat(concat, explanation[a_loopfield n%a_index%])
									if (concat != "")
										explanation[cell n4] := concat explanation[cell n4]
								}
								if (eliminate(cell, n4) = "one left")
									return
							}
						}
					}
				}
			}
		}
		;---- one number, two or three possible cells, all in the same row or column AND block ----
		if (caller = "SetOne")
			for un, cells in PossibleCells
		{
			u := SubStr(un, 1, -1)
			n := SubStr(un, 0)
			if (PossibleCells[u n].length() = 2 or PossibleCells[u n].length() = 3)
				loop, 27
					if (a_index != u
					and InStr(UnitCells[a_index], PossibleCells[u n][1])
					and InStr(UnitCells[a_index], PossibleCells[u n][2])
					and (PossibleCells[u n].length() = 2 or InStr(UnitCells[a_index], PossibleCells[u n][3])))
			{
				u2 := a_index
				loop, parse, % UnitCells[u2], -
					if (not InStr(UnitCells[u], a_loopfield) and number[a_loopfield] = 0 and InStr(PossibleNumbers[a_loopfield], n))
				{
					rc := a_loopfield
					if (caller = "SetOne")
					{
						text := n " can't be in " rc " <red> because in " unit[u] " it must be in "
						loop, parse, % UnitCells[u], -
							if (InStr(UnitCells[u2], a_loopfield) and InStr(AllPossibleNumbers[a_loopfield], n))
								text .= a_loopfield " <blue> or "
						text := SubStr(text, 1, -4)
						explanation[rc n] := text ".$"
						concat := ""
						loop, parse, % UnitCells[u], -
							if not InStr(UnitCells[u2], a_loopfield)
								ExplConcat(concat, explanation[a_loopfield n])
						if (concat != "")
							explanation[rc n] := concat explanation[rc n]
					}
					if (eliminate(rc, n) = "one left")
						return
				}
				break
			}
		}
		;---- X-wing ----
		for un, cells in PossibleCells
		{
			u := SubStr(un, 1, -1)
			n := SubStr(un, 0)
			if (PossibleCells[u n].length() = 2)
			{
				A := PossibleCells[u n][1]
				B := PossibleCells[u n][2]
				loop, % u-1
				{
					u2 := a_index
					C := PossibleCells[u2 n][1]
					D := PossibleCells[u2 n][2]
					if (PossibleCells[u2 n].length() = 2 and A != C and A != D and B != C and B != D)
					{
						for i, pattern in [A B C D, B A C D, A B D C, B A D C]
						{
							uCell1 := SubStr(pattern, 1, 2)
							uCell2 := SubStr(pattern, 3, 2)
							u2Cell1 := SubStr(pattern, 5, 2)
							u2Cell2 := SubStr(pattern, 7)
							if (InStr(ConnectedCells[uCell1], u2Cell1)
							and not InStr(ConnectedCells[uCell2], u2Cell1)
							and not InStr(ConnectedCells[u2Cell2], uCell1))
							{
								loop, parse, % ConnectedCells[uCell2], -
									if (InStr(ConnectedCells[u2Cell2], a_loopfield)
									and number[a_loopfield] = 0
									and InStr(PossibleNumbers[a_loopfield], n))
									{
										rc := a_loopfield
										if (caller = "SetOne")
										{
											explanation[rc n] := n
. " can't be in " rc " <red> because of the ""X-wing"" " u2Cell1 " - " u2Cell2 "/" uCell1 " - " uCell2 ":"
. "`n- If in " unit[u2] " " n " is in " u2Cell1 " <blue>, then in " unit[u] " it must be in " uCell2 " <blue>."
. "`n- If in " unit[u] " " n " is in " uCell1 " <blue>, then in " unit[u2] " it must be in " u2Cell2 " <blue>."
. "`nEither way " n " can't be in " rc " because " rc " is connected with both " uCell2 " and " u2Cell2 ".$"
											concat := ""
											loop, parse, % UnitCells[u], -
												if (a_loopfield != PossibleCells[u n][1] and a_loopfield != PossibleCells[u n][2])
													ExplConcat(concat, explanation[a_loopfield n])
											loop, parse, % UnitCells[u2], -
												if (a_loopfield != PossibleCells[u2 n][1] and a_loopfield != PossibleCells[u2 n][2])
													ExplConcat(concat, explanation[a_loopfield n])
											if (concat != "")
												explanation[rc n] := concat explanation[rc n]
										}
										if (eliminate(rc, n) = "one left")
											return
									}
							}
						}
					}
				}
			}
		}
		;---- Empty rectangle ----
		if (caller = "SetOne")
			for un, cells in PossibleCells
		{
			u := SubStr(un, 1, -1)
			n := SubStr(un, 0)
			if (u > 18 and PossibleCells[u n].length() >= 3 and PossibleCells[u n].length() <= 5)
				for i1, road1 in UnitRows[u]
					for i2, road2 in UnitColumns[u]
						if (i2 > i1)
			{
				crossroads := UnitCells[road1] "-" UnitCells[road2]
				AllInCrossroads := 1
				for i, cell in PossibleCells[u n]
					if not InStr(crossroads, cell)
				{
					AllInCrossroads := 0
					break
				}
				if AllInCrossroads
				{
					InRoad1 := 0
					InRoad2 := 0
					for i, cell in PossibleCells[u n]
					{
						if InStr(UnitCells[road1], cell)
							InRoad1 += 1
						if InStr(UnitCells[road2], cell)
							InRoad2 += 1
					}
					if (InRoad1 >= 2 and InRoad2 >= 2)
					{
						loop, 2
						{
							i_1 := a_index
							i_2 := i_1=1 ? 2 : 1
							AllEliminations := []
							QueuedEliminations := []
							IfThen := {}
							IfThenElement := []
							IfThenBefore := {}
							IfThenBeforeElement := []
							ExplBefore := {}
							ExplBeforeElement := []
							CopyArray(PossibleNumbers, "CopyPossibleNumbers", 1)
							CopyArray(PossibleCells, "CopyPossibleCells", 2)
							loop, parse, % UnitCells[road%i_1%] "-" UnitCells[road%i_2%], -
								if (InStr(UnitCells[road%i_1%], a_loopfield) and not InStr(UnitCells[u], a_loopfield)
								or InStr(UnitCells[road%i_2%], a_loopfield) and InStr(UnitCells[u], a_loopfield) and not InStr(UnitCells[road%i_1%], a_loopfield))
								if number[a_loopfield] = 0 and InStr(PossibleNumbers[a_loopfield], n)
									AllEliminations[1] .= "-" a_loopfield n
							AllEliminations[1] := LTrim(AllEliminations[1], "-")
							QueuedEliminations[1] := AllEliminations[1]
							loop, parse, % AllEliminations[1], -
							{
								rc := SubStr(a_loopfield, 1, 2)
								eliminate(rc, n, 0)
								IfThenElement[1] := "If " n " is in " unit[u] " and " unit[road%i_1%] ", then " rc " <blue> can't be " n
								IfThen[rc n] := IfThenElement[1] ".`n"
								IfThenBeforeElement[1] := ""
								ExplBeforeElement[1] := ""
								ContinueEliminating(rc, n, 2)
							}
							AllEliminations%i_1% := ""
							loop, 9
								if (AllEliminations[a_index] != "")
									AllEliminations%i_1% .= AllEliminations[a_index]
							AllEliminations%i_1% := LTrim(AllEliminations%i_1%, "-")
							CopyArray(IfThen, "IfThen" i_1, 1)
							CopyArray(IfThenBefore, "IfThenBefore" i_1, 1)
							CopyArray(ExplBefore, "ExplBefore" i_1, 1)
							CopyArray(CopyPossibleNumbers, "PossibleNumbers", 1)
							CopyArray(CopyPossibleCells, "PossibleCells", 2)
						}
						TextCrossRoads := ""
						for i, cell in PossibleCells[u n]
							TextCrossRoads .= " or " cell " <blue>"
						TextCrossRoads := SubStr(TextCrossRoads, 5)
						; Concatenate TextCrossRoads before loop, parse, AllEliminations1.
						; Loop, parse, AllEliminations1 might eliminate cells in PossibleCells[u n].
						loop, parse, AllEliminations1, -
							if InStr(AllEliminations2, a_loopfield)
						{
							EliminateWhat := SubStr(a_loopfield, 3, 1)
							EliminateIn := SubStr(a_loopfield, 1, 2)
							if (caller = "SetOne")
							{
								explanation[EliminateIn EliminateWhat] := EliminateWhat " can't be in " EliminateIn " <red> because of the ""Empty rectangle"" in " unit[u] ": "
								. "In " unit[u] ", " n " must be in " TextCrossRoads ", that is, in " unit[road1] " or " unit[road2] ".`n"
								loop, 2
									explanation[EliminateIn EliminateWhat] .= IfThenBefore%a_index%[EliminateIn EliminateWhat] IfThen%a_index%[EliminateIn EliminateWhat]
								MergeIfThen(6)  ; MergeIfThen(6) still allows MergeIfThen(4) etc.
								MergeIfThen(4)
								MergeIfThen(2)
								explanation[EliminateIn EliminateWhat] .= "$"
								concat := ""
								loop, parse, % UnitCells[u], -
									if (number[a_loopfield] = 0 and not InStr(TextCrossRoads, a_loopfield))  ; no other possible cells
										ExplConcat(concat, explanation[a_loopfield n])
								explanation[EliminateIn EliminateWhat] := concat ExplBefore1[EliminateIn EliminateWhat] ExplBefore2[EliminateIn EliminateWhat] explanation[EliminateIn EliminateWhat]
							}
							if (eliminate(EliminateIn, EliminateWhat) = "one left")
								return
						}
					}
				}
			}
		}
		;---- Y-wing chains ----
		if (caller = "SetOne" and main_index > 2)
			loop, parse, CellsWith2PossibleNumbers, -
		{
			rc := a_loopfield
			n1 := SubStr(PossibleNumbers[rc], 1, 1)
			n2 := SubStr(PossibleNumbers[rc], 2, 1)
			loop, 2
			{
				i_1 := a_index
				AllEliminations := []
				QueuedEliminations := []
				IfThen := {}
				IfThenElement := []
				IfThenBefore := {}
				IfThenBeforeElement := []
				ExplBefore := {}
				ExplBeforeElement := []
				CopyArray(PossibleNumbers, "CopyPossibleNumbers", 1)
				CopyArray(PossibleCells, "CopyPossibleCells", 2)
				loop, parse, % ConnectedCells[rc], -
					if number[a_loopfield] = 0 and InStr(PossibleNumbers[a_loopfield], n%i_1%)
						AllEliminations[1] .= "-" a_loopfield n%i_1%
				AllEliminations[1] := LTrim(AllEliminations[1], "-")
				QueuedEliminations[1] := AllEliminations[1]
				loop, parse, % AllEliminations[1], -
				{
					rc2 := SubStr(a_loopfield, 1, 2)
					eliminate(rc2, n%i_1%, 0)
					IfThenElement[1] := "If " rc " is " n%i_1% ", then " rc2 " <blue> can't be " n%i_1%
					IfThen[rc2 n%i_1%] := IfThenElement[1] ".`n"
					IfThenBeforeElement[1] := ""
					ExplBeforeElement[1] := ""
					ContinueEliminating(rc2, n%i_1%, 2)
				}
				AllEliminations%i_1% := ""
				loop, 9
					if (AllEliminations[a_index] != "")
						AllEliminations%i_1% .= AllEliminations[a_index]
				AllEliminations%i_1% := LTrim(AllEliminations%i_1%, "-")
				CopyArray(IfThen, "IfThen" i_1, 1)
				CopyArray(IfThenBefore, "IfThenBefore" i_1, 1)
				CopyArray(ExplBefore, "ExplBefore" i_1, 1)
				CopyArray(CopyPossibleNumbers, "PossibleNumbers", 1)
				CopyArray(CopyPossibleCells, "PossibleCells", 2)
			}
			loop, parse, AllEliminations1, -
				if InStr(AllEliminations2, a_loopfield)
			{
				EliminateWhat := SubStr(a_loopfield, 3, 1)
				EliminateIn := SubStr(a_loopfield, 1, 2)
				if (caller = "SetOne")
				{
					explanation[EliminateIn EliminateWhat] := EliminateWhat " can't be in " EliminateIn " <red> "
					. "because of the Y-wing chains starting with " n1 " and " n2 " in " rc " <blue>:`n"
					loop, 2
						explanation[EliminateIn EliminateWhat] .= IfThenBefore%a_index%[EliminateIn EliminateWhat] IfThen%a_index%[EliminateIn EliminateWhat]
					MergeIfThen(6)
					MergeIfThen(4)
					MergeIfThen(2)
					explanation[EliminateIn EliminateWhat] .= "$"
					concat := ""
					loop, 9
						if (a_index != n1 and a_index != n2)  ; no other possible numbers in rc
							ExplConcat(concat, explanation[rc a_index])
					explanation[EliminateIn EliminateWhat] := concat ExplBefore1[EliminateIn EliminateWhat] ExplBefore2[EliminateIn EliminateWhat] explanation[EliminateIn EliminateWhat]
				}
				if (eliminate(EliminateIn, EliminateWhat) = "one left")
					return
			}
		}
		;---- Swordfish/Jellyfish ----
		if (caller = "SetOne")
			loop, 9
		{
			n := a_index
			for i, A in ["A", "B"]
			{
				B := A="A" ? "B" : "A"
				line%A% := "row"
				pos%A% := 1
				plus%A% := 0
				letter%A% := [1,2,3,4,5,6,7,8,9]
				line%B% := "column"
				pos%B% := 2
				plus%B% := 9
				letter%B% := letter
				%lineA% := []
				%lineA%[1] := ""
				%lineB% := []
				%lineB%[1] := ""
				if fishnet(2)
					return
			}
		}
		if (main_index > 3)
		{
			change := 0
			for rc, n in PossibleNumbers
				if (PossibleNumbers[rc] != vorher[rc])
			{
				change := 1
				break
			}
			if not change
				break
		}
		if (main_index > 2)
			CopyArray(PossibleNumbers, "vorher", 1)
	}
	min := 10
	for rc, numbers in AllPossibleNumbers
	{
		if (StrLen(numbers) = 2)
		{
			next := {rc: rc, n: numbers, action: "several possible numbers"}
			break
		}
		else if (StrLen(numbers) < min)
		{
			next := {rc: rc, n: numbers, action: "several possible numbers"}
			min := StrLen(numbers)
		}
	}
}

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

ContinueEliminating(rc, n, index)
{
	local i, i2, u, u2, rc2, n1, n2, n_i, un, cell, cells
	; ---- one possible number ----
	if (StrLen(PossibleNumbers[rc]) = 1)
	{
		n2 := PossibleNumbers[rc]
		NextElements(index)
		IfThenElement[index] .= ", " rc " must be " n2
		concat1 := ""
		concat2 := ""
		concat3 := ""
		concat4 := ""
		loop, 9
			if (a_index != n and a_index != n2)  ; no other possible numbers
		{
			ExplConcat(concat1, IfThenBefore[rc a_index])
			ExplConcat(concat2, IfThen[rc a_index])
			ExplConcat(concat3, ExplBefore[rc a_index])
			ExplConcat(concat4, explanation[rc a_index])
		}
		IfThenBeforeElement[index] .= concat1 concat2
		ExplBeforeElement[index] .= concat3 concat4
		; ---- eliminate n2 in connected cells ----
		NewEliminations := ""
		loop, parse, % ConnectedCells[rc], -
			if (not InStr(IfThenElement[index], a_loopfield)
			and not InStr(QueuedEliminations[index-1], a_loopfield n2)
			and number[a_loopfield] = 0
			and InStr(PossibleNumbers[a_loopfield], n2))
		{
			NewEliminations .= "-" a_loopfield
			AllEliminations[index] .= "-" a_loopfield n2
			QueuedEliminations[index] .= "-" a_loopfield n2
		}
		NewEliminations := LTrim(NewEliminations, "-")
		loop, parse, NewEliminations, -
		{
			eliminate(a_loopfield, n2, 0)
			NextElements(index+1)
			IfThenElement[index+1] .= ", " a_loopfield " <blue> can't be " n2
			IfThen[a_loopfield n2] := IfThenElement[index+1] ".`n"
			IfThenBefore[a_loopfield n2] := IfThenBeforeElement[index+1]
			ExplBefore[a_loopfield n2] := ExplBeforeElement[index+1]
			if (index = 2)
				ContinueEliminating(a_loopfield, n2, index+2)
		}
	}
	; ---- one possible cell ----
	for i, u in [SubStr(rc, 1, 1), SubStr(rc, 2, 1)+9, block[rc]]
		if (PossibleCells[u n].length() = 1 and not InStr(IfThenElement[index-1], PossibleCells[u n][1]))
	{
		rc2 := PossibleCells[u n][1]
		NextElements(index)
		IfThenElement[index] .= ", " rc2 " <blue> is the only possible cell for " n " in " unit[u]
		concat1 := ""
		concat2 := ""
		concat3 := ""
		concat4 := ""
		loop, parse, % UnitCells[u], -
			if (a_loopfield != rc and a_loopfield != rc2 and number[a_loopfield] = 0)  ; no other possible cells
		{
			ExplConcat(concat1, IfThenBefore[a_loopfield n])
			ExplConcat(concat2, IfThen[a_loopfield n])
			ExplConcat(concat3, ExplBefore[a_loopfield n])
			ExplConcat(concat4, explanation[a_loopfield n])
		}
		IfThenBeforeElement[index] .= concat1 concat2
		ExplBeforeElement[index] .= concat3 concat4
		; ---- eliminate n in connected cells ----
		NewEliminations := ""
		loop, parse, % ConnectedCells[rc2], -
			if (not InStr(UnitCells[u], a_loopfield)
			and not InStr(IfThenElement[index], a_loopfield)
			and not InStr(QueuedEliminations[index-1], a_loopfield n)
			and number[a_loopfield] = 0
			and InStr(PossibleNumbers[a_loopfield], n))
		{
			NewEliminations .= "-" a_loopfield
			AllEliminations[index] .= "-" a_loopfield n
			QueuedEliminations[index] .= "-" a_loopfield n
		}
		NewEliminations := LTrim(NewEliminations, "-")
		loop, parse, NewEliminations, -
		{
			eliminate(a_loopfield, n, 0)
			NextElements(index+1)
			IfThenElement[index+1] .= ", " a_loopfield " <blue> can't be " n
			IfThen[a_loopfield n] := IfThenElement[index+1] ".`n"
			IfThenBefore[a_loopfield n] := IfThenBeforeElement[index+1]
			ExplBefore[a_loopfield n] := ExplBeforeElement[index+1]
			if (index = 2)
				ContinueEliminating(a_loopfield, n, index+2)
		}
	}
	; ---- two possible numbers in rc ----
	if (StrLen(PossibleNumbers[rc]) = 2)
	{
		n1 := SubStr(PossibleNumbers[rc], 1, 1)
		n2 := SubStr(PossibleNumbers[rc], 2, 1)
		NextElements(index)
		IfThenElement[index] .= ", " n1 " and " n2 " are the only possible numbers in " rc " <blue>"
		concat1 := ""
		concat2 := ""
		concat3 := ""
		concat4 := ""
		loop, 9
			if (a_index != n and a_index != n1 and a_index != n2)  ; no other possible numbers
		{
			ExplConcat(concat1, IfThenBefore[rc a_index])
			ExplConcat(concat2, IfThen[rc a_index])
			ExplConcat(concat3, ExplBefore[rc a_index])
			ExplConcat(concat4, explanation[rc a_index])
		}
		IfThenBeforeElement[index] .= concat1 concat2
		ExplBeforeElement[index] .= concat3 concat4
		; ---- same two possible numbers in rc2 ----
		loop, parse, % ConnectedCells[rc], -
			if (not InStr(IfThenElement[index], a_loopfield) and number[a_loopfield] = 0 and PossibleNumbers[a_loopfield] = PossibleNumbers[rc])
		{
			rc2 := a_loopfield
			loop, 9
				if (a_index != n1 and a_index != n2)  ; no other possible numbers
			{
				ExplConcat(concat1, IfThenBefore[rc2 a_index])
				ExplConcat(concat2, IfThen[rc2 a_index])
				ExplConcat(concat3, ExplBefore[rc2 a_index])
				ExplConcat(concat4, explanation[rc2 a_index])
			}
			IfThenBeforeElement[index] .= concat1 concat2
			ExplBeforeElement[index] .= concat3 concat4
			; ---- eliminate both numbers in connected cells ----
			NewEliminations1 := ""
			NewEliminations2 := ""
			loop, parse, % ConnectedCells[rc], -
				if (InStr(ConnectedCells[rc2], a_loopfield)
				and not InStr(IfThenElement[index], a_loopfield)
				and number[a_loopfield] = 0
				and InStr(PossibleNumbers[a_loopfield], n1)
				and InStr(PossibleNumbers[a_loopfield], n2)
				and not InStr(QueuedEliminations[index-1], a_loopfield n1)
				and not InStr(QueuedEliminations[index-1], a_loopfield n2))
			{
				NewEliminations1 .= "-" a_loopfield
				NewEliminations2 .= "-" a_loopfield
				AllEliminations[index] .= "-" a_loopfield n1 "-" a_loopfield n2
				QueuedEliminations[index] .= "-" a_loopfield n1 "-" a_loopfield n2
			}
			NewEliminations1 := LTrim(NewEliminations1, "-")
			NewEliminations2 := LTrim(NewEliminations2, "-")
			for i, n_i in [n1, n2]
				loop, parse, NewEliminations%i%, -
			{
				eliminate(a_loopfield, n_i, 0)
				NextElements(index+1)
				IfThenElement[index+1] .= " and " rc2 " <blue>, " a_loopfield " <blue> can't be " n_i
				IfThen[a_loopfield n_i] := IfThenElement[index+1] ".`n"
				IfThenBefore[a_loopfield n_i] := IfThenBeforeElement[index+1]
				ExplBefore[a_loopfield n_i] := ExplBeforeElement[index+1]
				if (index = 2)
					ContinueEliminating(a_loopfield, n_i, index+2)
			}
			break
		}
	}
	; ---- two possible cells for n ----
	for i, u in [SubStr(rc, 1, 1), SubStr(rc, 2, 1)+9, block[rc]]
		if (PossibleCells[u n].length() = 2 and EmptyCells[u] > 2
		and not InStr(IfThenElement[index-1], PossibleCells[u n][1])
		and not InStr(IfThenElement[index-1], PossibleCells[u n][2]))
	{
		NextElements(index)
		IfThenElement[index] .= ", " PossibleCells[u n][1] " <blue> and " PossibleCells[u n][2] " <blue> are the only possible cells for " n
		concat1 := ""
		concat2 := ""
		concat3 := ""
		concat4 := ""
		loop, parse, % UnitCells[u], -
			if (a_loopfield != rc and a_loopfield != PossibleCells[u n][1] and a_loopfield != PossibleCells[u n][2] and number[a_loopfield] = 0)  ; no other possible cells
		{
			ExplConcat(concat1, IfThenBefore[a_loopfield n])
			ExplConcat(concat2, IfThen[a_loopfield n])
			ExplConcat(concat3, ExplBefore[a_loopfield n])
			ExplConcat(concat4, explanation[a_loopfield n])
		}
		IfThenBeforeElement[index] .= concat1 concat2
		ExplBeforeElement[index] .= concat3 concat4
		; ---- same two possible cells for n2 ----
		loop, 9
			if (a_index != n
			and PossibleCells[u a_index].length() = 2
			and PossibleCells[u a_index][1] = PossibleCells[u n][1]
			and PossibleCells[u a_index][2] = PossibleCells[u n][2])
		{
			n2 := a_index
			IfThenElement[index] .= " and " n2 " in " unit[u]
			loop, parse, % UnitCells[u], -
				if (a_loopfield != PossibleCells[u n2][1] and a_loopfield != PossibleCells[u n2][2] and number[a_loopfield] = 0)  ; no other possible cells
			{
				ExplConcat(concat1, IfThenBefore[a_loopfield n2])
				ExplConcat(concat2, IfThen[a_loopfield n2])
				ExplConcat(concat3, ExplBefore[a_loopfield n2])
				ExplConcat(concat4, explanation[a_loopfield n2])
			}
			IfThenBeforeElement[index] .= concat1 concat2
			ExplBeforeElement[index] .= concat3 concat4
			; ---- eliminate other numbers in the two possible cells ----
			NewEliminations := ""
			for i2, cell in PossibleCells[u n]
			{
				loop, parse, % PossibleNumbers[cell]
					if (a_loopfield != n and a_loopfield != n2 and not InStr(QueuedEliminations[index-1], cell a_loopfield))
				{
					NewEliminations .= "-" a_loopfield
					AllEliminations[index] .= "-" cell a_loopfield
					QueuedEliminations[index] .= "-" cell a_loopfield
				}
				NewEliminations := LTrim(NewEliminations, "-")
				loop, parse, NewEliminations, -
				{
					n3 := a_loopfield
					eliminate(cell, n3, 0)
					NextElements(index+1)
					IfThenElement[index+1] .= ", " cell " <blue> can't be " n3
					IfThen[cell n3] := IfThenElement[index+1] ".`n"
					IfThenBefore[cell n3] := IfThenBeforeElement[index+1]
					ExplBefore[cell n3] := ExplBeforeElement[index+1]
					if (index = 2)
						ContinueEliminating(cell, n3, index+2)
				}
			}
			break
		}
	}
	; ---- two or three possible cells for n in u ----
	for i, u in [SubStr(rc, 1, 1), SubStr(rc, 2, 1)+9, block[rc]]
		if (PossibleCells[u n].length() = 2 or PossibleCells[u n].length() = 3
		and not InStr(IfThenElement[index-1], PossibleCells[u n][1])
		and not InStr(IfThenElement[index-1], PossibleCells[u n][2])
		and (PossibleCells[u n].length() = 2 or not InStr(IfThenElement[index-1], PossibleCells[u n][3])))
	{
		PossibleCellsText := PossibleCells[u n][1] " <blue> and " PossibleCells[u n][2] " <blue>"
		if (PossibleCells[u n].length() = 3)
			PossibleCellsText .= " and " PossibleCells[u n][3] " <blue>"
		NextElements(index)
		IfThenElement[index] .= ", that leaves " PossibleCellsText " as possible cells for " n " in " unit[u]
		concat1 := ""
		concat2 := ""
		concat3 := ""
		concat4 := ""
		loop, parse, % UnitCells[u], -
			if (not InValues(PossibleCells[u n], a_loopfield) and number[a_loopfield] = 0)  ; no other possible cells
		{
			ExplConcat(concat1, IfThenBefore[a_loopfield n])
			ExplConcat(concat2, IfThen[a_loopfield n])
			ExplConcat(concat3, ExplBefore[a_loopfield n])
			ExplConcat(concat4, explanation[a_loopfield n])
		}
		IfThenBeforeElement[index] .= concat1 concat2
		ExplBeforeElement[index] .= concat3 concat4
		; ---- all of them also in u2 ----
		loop, 27
			if (a_index != u
			and InStr(UnitCells[a_index], PossibleCells[u n][1])
			and InStr(UnitCells[a_index], PossibleCells[u n][2])
			and (PossibleCells[u n].length() = 2 or InStr(UnitCells[a_index], PossibleCells[u n][3])))
		{
			u2 := a_index
			; ---- eliminate n in other cells in u2 ----
			NewEliminations := ""
			loop, parse, % UnitCells[u2], -
				if (not InStr(IfThenElement[index], a_loopfield)
				and not InStr(QueuedEliminations[index-1], a_loopfield n)
				and number[a_loopfield] = 0
				and InStr(PossibleNumbers[a_loopfield], n))
			{
				NewEliminations .= "-" a_loopfield
				AllEliminations[index] .= "-" a_loopfield n
				QueuedEliminations[index] .= "-" a_loopfield n
			}
			NewEliminations := LTrim(NewEliminations, "-")
			loop, parse, NewEliminations, -
			{
				eliminate(a_loopfield, n, 0)
				NextElements(index+1)
				IfThenElement[index+1] .= ", " a_loopfield " <blue> can't be " n
				IfThen[a_loopfield n] := IfThenElement[index+1] ".`n"
				IfThenBefore[a_loopfield n] := IfThenBeforeElement[index+1]
				ExplBefore[a_loopfield n] := ExplBeforeElement[index+1]
				if (index = 2)
					ContinueEliminating(a_loopfield, n, index+2)
			}
			break
		}
	}
}

;========================================================================================
;	Other subroutines A-Z
;========================================================================================

AllValues(array)  ; for arrays with cells as keys
{
	global
	string := ""
	loop, parse, AllCells, -
		string .= array[a_loopfield] "-"
	return string
}

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

ArrangeWindows(win2)
{
	global
	SysGet, screen, MonitorWorkArea
	wingetpos, win1left, win1top, win1width, win1height, %WinTitle% ahk_class AutoHotkeyGUI
	win1right := win1left+win1width
	win1bottom := win1top+win1height
	win2width := %win2%width  ; Win2GuiSize gets width and height of win2
	win2height := %win2%height
	if (win1right+10+win2width < screenright)
	{
		if (win1top+win2height < screenbottom)
			gui, %win2%:show, % "x" win1right+10 "y" win1top
		else
			gui, %win2%:show, % "x" win1right+10 "y" screenbottom-win2height
	}
	else if (win1bottom+10+win2height < screenbottom)
	{
		if (win1left+win2width < screenright)
			gui, %win2%:show, % "x" win1left "y" win1bottom+10
		else
			gui, %win2%:show, % "x" screenright-win2width "y" win1bottom+10
	}
	else if (win1left-10-win2width > 0)
	{
		if (win1top+win2height < screenbottom)
			gui, %win2%:show, % "x" win1left-10-win2width "y" win1top
		else
			gui, %win2%:show, % "x" win1left-10-win2width "y" screenbottom-win2height
	}
	else if (win1top-10-win2height > 0)
	{
		if (win1left+win2width < screenright)
			gui, %win2%:show, % "x" win1left "y" win1top-10-win2height
		else
			gui, %win2%:show, % "x" screenright-win2width "y" win1top-10-win2height
	}
	else if (win1width+10+win2width < screenright)
	{
		gui, 1:show, % "x" screenright-(win1width+10+win2width)
		if (win1top+win2height < screenbottom)
			gui, %win2%:show, % "x" screenright-win2width "y" win1top
		else
			gui, %win2%:show, % "x" screenright-win2width "y" screenbottom-win2height
	}
	else if (win1height+10+win2height < screenbottom)
	{
		gui, 1:show, % "y" screenbottom-(win1height+10+win2height)
		if (win1left+win2width < screenright)
			gui, %win2%:show, % "x" win1left "y" screenbottom-win2height
		else
			gui, %win2%:show, % "x" screenright-win2width "y" screenbottom-win2height
	}
	else
	{
		ZoomFitWidth := zoom*(screenright-win2width-10-(win1width-9.4*wWhiteSquare))/(9.4*wWhiteSquare)
		ZoomFitHeight := zoom*(screenbottom-win2height-10-(win1height-9.4*wWhiteSquare))/(9.4*wWhiteSquare)
		; 9.4*wWhiteSquare = 9*wWhiteSquare+wCaption, win1width-9.4*wWhiteSquare = grid lines+borders, win1height-9.4*wWhiteSquare = title bar+menu bar+grid lines+borders
		if (ZoomFitWidth < 0.48 and ZoomFitHeight < 0.48)
		{
			gui, 1:hide
			SizeAndPosition(0.5-zoom)
			gui, 1:show, y0 w%wGui% h%wGui%
			gui, %win2%:show, % "y" screenbottom-win2height
		}
		else if (ZoomFitWidth > ZoomFitHeight)
		{
			gui, 1:hide
			SizeAndPosition(ZoomFitWidth-zoom)
			gui, 1:show, x0 w%wGui% h%wGui%
			if (win1top+win2height < screenbottom)
				gui, %win2%:show, % "x" wGui+10 "y" win1top
			else
				gui, %win2%:show, % "x" wGui+10 "y" screenbottom-win2height
		}
		else
		{
			gui, 1:hide
			SizeAndPosition(ZoomFitHeight-zoom)
			gui, 1:show, y0 w%wGui% h%wGui%
			if (win1left+win2width < screenright)
				gui, %win2%:show, % "x" win1left "y" hTitleMenu+wGui+10
			else
				gui, %win2%:show, % "x" screenright-win2width "y" hTitleMenu+wGui+10
		}
	}
}

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

ArrayAdd(array1, array2)
{
	local i, element
	array1_2 := []
	loop, 2
		for i, element in array%a_index%
			if not InValues(array1_2, element)
				array1_2.push(element)
	return array1_2
}

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

AutoColor(u)
{
	if u <= 9
		return "red"
	else if u <= 18
		return "green"
	else
		return "blue"
}

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

AutoPencil()
{
	local rc, u, n, un, cells
	if not AutoPencil
		return
	GetAllPossibleNumbers()
	GetPossibleCells("All")
	;---- reset PencilMark and cPencilMark ----
	loop, parse, AllCells, -
		if (number[a_loopfield] = 0)
			loop, 9
	{
		PencilMark[a_loopfield a_index] := 0
		cPencilMark[a_loopfield a_index] := "default"
	}
	;---- get singles/pairs and set cPencilMark ----
	SinglesOrPairs := {}
	for un, cells in AllPossibleCells
		if (cells.length() = 1)
	{
		u := SubStr(un, 1, -1)
		n := SubStr(un, 0)
		if not InStr(SinglesOrPairs[cells[1]], n)  ; Red overwrites cDefault, green doesn't overwrite red, blue doesn't overwrite green.
		{
			SinglesOrPairs[cells[1]] .= n
			cPencilMark[cells[1] n] := AutoColor(u)
		}
	}
	for rc, n in AllPossibleNumbers
		if (StrLen(n) = 1)
			if not InStr(SinglesOrPairs[rc], n)
				SinglesOrPairs[rc] .= n
	if (SinglesOrPairs.length() = 0)
	{
		for un, cells in AllPossibleCells
			if (cells.length() = 2)
		{
			u := SubStr(un, 1, -1)
			n := SubStr(un, 0)
			loop, 2
				if not InStr(SinglesOrPairs[cells[a_index]], n)
			{
				SinglesOrPairs[cells[a_index]] .= n
				cPencilMark[cells[a_index] n] := AutoColor(u)
			}
		}
		for rc, n in AllPossibleNumbers
			if (StrLen(n) = 2)
				loop, parse, n
					if not InStr(SinglesOrPairs[rc], a_loopfield)
						SinglesOrPairs[rc] .= a_loopfield
	}
	;---- set PencilMark ----
	for rc, n in %AutoPencil%  ; AutoPencil = "AllPossibleNumbers" or "SinglesOrPairs"
		loop, parse, n
			PencilMark[rc a_loopfield] := 1
	;---- apply PencilMark and cPencilMark ----
	loop, parse, AllCells, -
		if (number[a_loopfield] = 0)
			loop, 9
				PencilMark(a_loopfield, a_index, PencilMark[a_loopfield a_index], cPencilMark[a_loopfield a_index])
}

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

background(color)
{
	global
	color := hex(color)
	if (cBackground != color)
	{
		cBackground := color
		cBackgroundFor%display% := color
		gui, 1:color, %cBackground%
		color := SubStr(color, 3)			; omit "0x"
		loop, % 6-StrLen(color)				; add leading zeros
			color := "0" . color
		red := "0x" . SubStr(color, 1, 2)	; split into red, green, blue
		green := "0x" . SubStr(color, 3, 2)
		blue := "0x" . SubStr(color, 5, 2)
		if (floor(sqrt(2*red**2+6*green**2+blue**2)) > 1.3*floor(sqrt(2*(255-red)**2+6*(255-green)**2+(255-blue)**2)))
		{
			cDefault := "black"
			cFixed := "0x007000"
		}
		else
		{
			cDefault := "0xDFDFDF"
			cFixed := "0xFFE070"
		}
		captions(cDefault)
	}
}

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

captions(color)
{
	global
	loop, parse, caption123, -
		font("caption123" a_loopfield, color, sCaption, "Ubuntu", "Arial")
	loop, parse, captionABC, -
		font("captionABC" a_loopfield, color, sCaption, "Ubuntu", "Arial")
}

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

color(c)
{
	local i, p
	MouseGetCell()
	if (LastAction["action"] = "number"
	and A_TickCount - LastAction["time"] < 3000)
	{
		set(LastAction["cell"], LastAction["n"], c)
		history()
	}
	else if (LastAction["action"] = "PencilMark"
	and A_TickCount - LastAction["time"] < 3000)
	{
		PencilMark(LastAction["cell"], LastAction["n"], 1, c)
		history()
	}
	else if (LastAction["action"] = "ColorPencilMark"
	and LastAction["c"] = c
	and A_TickCount - LastAction["time"] < 1000)
	{
		OtherPencilMarks := []
		loop, 9
			if (PencilMark[LastAction["cell"] a_index] = 1 and cPencilMark[LastAction["cell"] a_index] != c)
				OtherPencilMarks.push(a_index)
		for i, p in OtherPencilMarks
			if (p > LastAction["n"])
			{
				KeepLastAction := 1
				gosub back
				PencilMark(LastAction["cell"], p, 1, c)
				KeepLastAction := 0
				LastAction["n"] := p
				LastAction["time"] := A_TickCount
				history()
				break
			}
			else if (i = OtherPencilMarks.length())
			{
				KeepLastAction := 1
				gosub back
				PencilMark(LastAction["cell"], OtherPencilMarks[1], 1, c)
				KeepLastAction := 0
				LastAction["n"] := OtherPencilMarks[1]
				LastAction["time"] := A_TickCount
				history()
			}
	}
	else if (number[rm cm] != 0)
	{
		set(rm cm, number[rm cm], c)
		history()
	}
	else loop, 9
		if (PencilMark[rm cm a_index] = 1 and cPencilMark[rm cm a_index] != c)
	{
		PencilMark(rm cm, a_index, 1, c)
		LastAction := {action: "ColorPencilMark", cell: rm cm, n: a_index, c: c, time: A_TickCount}
		history()
		break
	}
}

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

CopyArray(array, CopyName, dimension)
{
	local key, index, element1, element2, array2
	%CopyName% := {}
	for key, element1 in array
	{
		if (dimension = 1)
			%CopyName%[key] := element1
		else if (dimension = 2)
		{
			array2 := []
			for index, element2 in element1
				array2.push(element2)
			%CopyName%[key] := array2
		}
	}
}

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

CreateImageSave:
if not FileExist(a_desktop "\Sudoku\Create image")
	FileCreateDir, %a_desktop%\Sudoku\Create image
if FileExist(a_desktop "\Sudoku\Create image\PencilImage.txt")
	filedelete, %a_desktop%\Sudoku\Create image\PencilImage.txt
fileappend, % history[HistoryIndex], %a_desktop%\Sudoku\Create image\PencilImage.txt
AllImagesString := ""
for i, image in AllImages
	AllImagesString .= image "-"
AllImagesString := RTrim(AllImagesString, "-")
if FileExist(a_desktop "\Sudoku\Create image\AllImages.txt")
	filedelete, %a_desktop%\Sudoku\Create image\AllImages.txt
fileappend, %AllImagesString%, %a_desktop%\Sudoku\Create image\AllImages.txt
loop, 60
	if (a_index >= 4)
{
	SortedImagesString := ""
	for i, image in SortedImages[a_index]
		SortedImagesString .= image "-"
	SortedImagesString := RTrim(SortedImagesString, "-")
	if FileExist(a_desktop "\Sudoku\Create image\SortedImages" a_index ".txt")
		filedelete, %a_desktop%\Sudoku\Create image\SortedImages%a_index%.txt
	fileappend, %SortedImagesString%, %a_desktop%\Sudoku\Create image\SortedImages%a_index%.txt
}
return

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

eliminate(rc, n, checkout := 1)
{
	local i1, i2, u, cell
	PossibleNumbers[rc] := StrReplace(PossibleNumbers[rc], n)
	if (checkout and StrLen(PossibleNumbers[rc]) = 1)
	{
		next := {rc: rc, n: PossibleNumbers[rc], action: "one possible number"}
		return "one left"
	}
	for i1, u in [SubStr(rc, 1, 1), SubStr(rc, 2, 1)+9, block[rc]]
		for i2, cell in PossibleCells[u n]
			if (cell = rc)
	{
		PossibleCells[u n].RemoveAt(i2)
		if (checkout and PossibleCells[u n].length() = 1)
		{
			next := {u: u, rc: PossibleCells[u n][1], n: n, action: "one possible cell"}
			return "one left"
		}
		break
	}
}

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

ExplConcat(byref concat, expl)
{
	if (expl != "" and not InStr(concat, expl))
		concat .= expl
}

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

fishnet(index)
{
	local i, rc
	loop, 9
	{
		%lineA%[index] := %lineA%[index-1]
		%lineB%[index] := %lineB%[index-1]
		if ((index = 2 or a_index > SubStr(%lineA%[index], 0)) and PossibleCells[a_index+plusA n].length() >= 2)
		{
			for i, rc in PossibleCells[a_index+plusA n]
				if not InStr(%lineB%[index], SubStr(rc, posB, 1))
					%lineB%[index] .= SubStr(rc, posB, 1)
			if (StrLen(%lineB%[index]) <= 4)
			{
				%lineA%[index] .= a_index
				if (index = 4 and StrLen(%lineB%[index]) = 3 or index = 5 and StrLen(%lineB%[index]) = 4)
				{
					loop, parse, % %lineB%[index]
						loop, parse, % UnitCells[a_loopfield+plusB], -
							if (not InStr(%lineA%[index], SubStr(a_loopfield, posA, 1)) and number[a_loopfield] = 0 and InStr(PossibleNumbers[a_loopfield], n))
					{
						rc := a_loopfield
						if (index = 4)
							fish := " ""Swordfish"" "
						else if (index = 5)
							fish := " ""Jellyfish"" "
						Text%lineA% := ""
						loop, parse, % %lineA%[index]
							Text%lineA% .= letterA[a_loopfield] "-"
						Text%lineA% := RTrim(Text%lineA%, "-")
						Text%lineB% := ""
						loop, parse, % %lineB%[index]
							Text%lineB% .= letterB[a_loopfield] "-"
						Text%lineB% := RTrim(Text%lineB%, "-")
						sort, Text%lineB%, d-
						TextPossibleCells := ""
						loop, % index-1
							loop, parse, % UnitCells[SubStr(%lineA%[index], a_index, 1)+plusA], -
								if (number[a_loopfield] = 0 and InStr(%lineB%[index], SubStr(a_loopfield, posB, 1)) and InStr(AllPossibleNumbers[a_loopfield], n))
									TextPossibleCells .= a_loopfield " <blue>, "
						TextPossibleCells := RTrim(TextPossibleCells, ", ")
						explanation[rc n] := n " can't be in " rc " <red> because of the" fish "in " lineA " " Text%lineA% "/" lineB " " Text%lineB% ":`n"
						. "In " lineA " " Text%lineA% ", " n " can be in " TextPossibleCells ". That's all in " lineB " " Text%lineB% ", and that's one " n " for each " lineB ", "
						. "so there can't be any more " n "s in these " lineB "s.$"
						concat := ""
						loop, % index-1
							loop, parse, % UnitCells[SubStr(%lineA%[index], a_index, 1)+plusA], -
								if not InStr(%lineB%[index], SubStr(a_loopfield, posB, 1))  ; no other lineB
									ExplConcat(concat, explanation[a_loopfield n])
						if (concat != "")
							explanation[rc n] := concat explanation[rc n]
						if (eliminate(rc, n) = "one left")
							return 1
					}
				}
				else if (index < 4 or main_index > 1 and index < 5)
					if fishnet(index+1)
						return 1
			}
		}
	}
}

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

font(TextControl, color, size, font1, font2 := "", weight := 400)
{
	global
	color := hex(color)
	if (cTextControl[TextControl] != color
	or sTextControl[TextControl] != size
	or wTextControl[TextControl] != weight
	or fTextControl[TextControl] != font1
	or HighlightOnTop)
	{
		gui, 1:font, w%weight% c%color% s%size%, %font1%
		if (font2 != "")
			gui, 1:font,, %font2%
		guicontrol, 1:font, %TextControl%
		guicontrol, 1:movedraw, %TextControl%
		cTextControl[TextControl] := color
		sTextControl[TextControl] := size
		wTextControl[TextControl] := weight
		fTextControl[TextControl] := font1
	}
}

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

GuiToString(caller := "")
{
	global
	string := display "/" cBackground "/"
	loop, parse, AllCells, -
		if (number[a_loopfield] != 0)
	{
		string .= a_loopfield number[a_loopfield]
		if (display = "numbers" and cNumber[a_loopfield] != "default")
			string .= cNumber[a_loopfield]
		string .= ","
	}
	string := StrReplace(string, "xyz")
	string := StrReplace(string, "0x")
	string := RTrim(string, ",")
	string .= "/"
	loop, parse, AllCells, -
		if (number[a_loopfield] = 0)
			loop, 9
				if (PencilMark[a_loopfield a_index] = 1)
	{
		string .= a_loopfield a_index
		if (display = "numbers" and cPencilMark[a_loopfield a_index] != "default")
			string .= cPencilMark[a_loopfield a_index]
		string .= ","
	}
	string := RTrim(string, ",")
	if (caller = "history")
		string .= "/" FixedCells
	return string
}

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

hex(color)
{
	global
	if InKeys(ColorValue, color)
		color := ColorValue[color]
	else if (SubStr(color, 1, 3) = "xyz")
		color := SubStr(color, 4)
	if (SubStr(color, 1, 2) != "0x")
		color := "0x" . color
	return color
}

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

highlight(rc, color, restore := 0)
{
	global
	if (highlight[rc] = color and not restore)
		return
	highlight[rc] := color
	if (color = "")
		text("WhiteSquare" rc, "")
	else
	{
		font("WhiteSquare" rc, HighlightValue[color], sHighlight, "Webdings")
		text("WhiteSquare" rc, "g")
	}
	HighlightOnTop := 1
	if (number[rc] != 0)
		set(rc, number[rc])
	else loop, 9
		if (PencilMark[rc a_index] = 1)
			PencilMark(rc, a_index, 1)
	HighlightOnTop := 0
}

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

history()
{
	global
	string := GuiToString("history")
	if (string != history[HistoryIndex])
	{
		if (history.MaxIndex() > HistoryIndex)
			loop, % history.MaxIndex()-HistoryIndex
				history.pop()
		history.push(string)
		HistoryIndex += 1
		if (history.MaxIndex() > 99)
		{
			history.RemoveAt(1)
			HistoryIndex -= 1
		}
		return 1
	}
}

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

InKeys(array, var)
{
	For key, value in array
		if (var = key)
			return 1
}

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

InValues(array, var)
{
	VarString := var . "x"
	For index, value in array
	{
		ValueString := value . "x"  ; "x" makes sure that var and value are compared as strings.
		if (VarString = ValueString)
			return 1
	}
}

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

intersection(u, r1, r2, r3, c1, c2, c3)
{
	global
	UnitCells[u] := ""
	loop, parse, AllCells, -
	{
		rc := a_loopfield
		r := SubStr(rc, 1, 1)
		c := SubStr(rc, 2, 1)
		if ((r = r1 or r = r2 or r = r3) and (c = c1 or c = c2 or c = c3))
			UnitCells[u] .= rc "-"
	}
	UnitCells[u] := RTrim(UnitCells[u], "-")
	UnitRows[u] := [r1, r2, r3]
	UnitColumns[u] := [c1+9, c2+9, c3+9]
}

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

item(n)
{
	global
	if (display = "colors")
		return ColorName[n]
	else
		return n
}

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

LeaveImage:
loop, parse, AllCells, -
	if not InStr(image, a_loopfield)
		set(a_loopfield, 0)
return

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

merge(array, MergeWhat)
{
	local rc, n, i1, i2, element1, element2
	n := []
	rc := []
	for i1, element1 in array
	{
		loop, parse, element1, %a_space%
		{
			if (a_index = 1)
				n[i1] := a_loopfield
			else if (a_index = 5)
			{
				rc[i1] := a_loopfield
				break
			}
		}
	}
	array2 := []
	skip := []
	for i1, element1 in array
	{
		if skip[i1]
			continue
		n_merge := n[i1]
		rc_merge := rc[i1] " <red>"
		for i2, element2 in array
			if (i2 > i1)
		{
			if (MergeWhat = "n")
			{
				string := StrReplace(array[i2], n[i2] " can't be in " rc[i2], n[i1] " can't be in " rc[i2])
				if InStr(string, "X-wing")
					string := StrReplace(string, n[i2] " is ", n[i1] " is ")
				else if InStr(string, "Empty rectangle") or InStr(string, "Y-wing")
					string := StrReplace(string, rc[i2] " <blue> can't be " n[i2], rc[i2] " <blue> can't be " n[i1])
				if (string = array[i1])
				{
					skip[i2] := 1
					n_merge .= " and " n[i2]
				}
			}
			else if (MergeWhat = "rc")
			{
				string := StrReplace(array[i2], n[i2] " can't be in " rc[i2], n[i2] " can't be in " rc[i1])
				if InStr(string, "X-wing")
					string := StrReplace(string, rc[i2] " is ", rc[i1] " is ")
				else if InStr(string, "Empty rectangle") or InStr(string, "Y-wing")
					string := StrReplace(string, rc[i2] " <blue> can't be " n[i2], rc[i1] " <blue> can't be " n[i2])
				if (string = array[i1])
				{
					skip[i2] := 1
					rc_merge .= " and " rc[i2] " <red>"
				}
			}
		}
		if (MergeWhat = "n" and InStr(n_merge, " and "))
		{
			array[i1] := StrReplace(array[i1], n[i1] " can't be in " rc[i1], n_merge " can't be in " rc[i1])
			if InStr(array[i1], "X-wing")
			{
				array[i1]:= StrReplace(array[i1], n[i1] " is ", n_merge " are ")
				array[i1]:= StrReplace(array[i1], " it ", " they ")
			}
			else if InStr(array[i1], "Empty rectangle") or InStr(array[i1], "Y-wing")
				array[i1] := StrReplace(array[i1], rc[i1] " <blue> can't be " n[i1], rc[i1] " can't be " n_merge)
		}
		else if (MergeWhat = "rc" and InStr(rc_merge, " and "))
		{
			array[i1] := StrReplace(array[i1], n[i1] " can't be in " rc[i1], n[i1] " can't be in " rc_merge)
			array[i1] := StrReplace(array[i1], "<red> <red>", "<red>")
			if InStr(array[i1], "X-wing")
				array[i1]:= StrReplace(array[i1], rc[i1] " is ", rc_merge " are ")
			else if InStr(array[i1], "Empty rectangle") or InStr(array[i1], "Y-wing")
				array[i1] := StrReplace(array[i1], rc[i1] " <blue> can't be " n[i1], rc_merge " can't be " n[i1])
		}
		array2.push(array[i1])
	}
	return array2
}

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

MergeIfThen(MergePos)
{
	local IfThen1, IfThen2, i1, i2, element1, element2
	IfThen1 := []
	loop, parse, % explanation[EliminateIn EliminateWhat], `n
		if (a_loopfield != "" and not InValues(IfThen1, a_loopfield))
			IfThen1.push(a_loopfield)
	text1 := []
	MergeText := []
	text2 := []
	for i1, element1 in IfThen1
		if (i1 > 1)
	{
		text1[i1] := ""
		MergeText[i1] := ""
		text2[i1] := ""
		loop, parse, element1, `,
		{
			if (a_index < MergePos)
				text1[i1] .= a_loopfield ","
			else if (a_index = MergePos)
			{
				loop, parse, a_loopfield, %a_space%
				{
					if (MergePos = 2)
					{
						if (a_index = 2)
							text1[i1] .= " " a_loopfield		; then ...
						else if (a_index = 3 or a_index = 4)
							MergeText[i1] .= " " a_loopfield	; ... rc <blue> ...
						else if (a_index > 4)
							text2[i1] .= " " a_loopfield		; ... can't be n
					}
					else
					{
						if (a_index = 2 or a_index = 3)
							MergeText[i1] .= " " a_loopfield	; rc <blue> ...
						else if (a_index > 3)
							text2[i1] .= " " a_loopfield		; ... can't be n
					}
				}
			}
			else if (a_loopfield != "")
				text2[i1] .= "," a_loopfield
		}
	}
	IfThen2 := []
	skip := []
	for i1, element1 in IfThen1
	{
		if (i1 = 1)
		{
			IfThen2.push(element1)
			continue
		}
		if skip[i1]
			continue
		merge := MergeText[i1]
		for i2, element2 in IfThen1
			if (i2 > i1
			and text1[i1] = text1[i2]
			and (InStr(text2[i1], RTrim(text2[i2], ".")) or InStr(text2[i2], RTrim(text2[i1], "."))))
		{
			skip[i2] := 1
			if not InStr(merge, MergeText[i2])
				merge .= "-" MergeText[i2]
			if not InStr(text2[i1], RTrim(text2[i2], "."))
				text2[i1] := text2[i2]
		}
		if InStr(merge, "-")
		{
			sort, merge, d-
			merge := StrReplace(merge, "-", " and")
		}
		IfThen2.push(text1[i1] merge text2[i1])
	}
	explanation[EliminateIn EliminateWhat] := ""
	for i2, element2 in IfThen2
		explanation[EliminateIn EliminateWhat] .= element2 "`n"
}

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

MouseClickCell(rc)
{
	global
	rm := SubStr(rc, 1, 1)
	cm := SubStr(rc, 2, 1)
	x := wBorder + pos[cm] + wWhiteSquare/2
	y := hTitleMenu + pos[rm] + wWhiteSquare/2
	click %x%, %y%, 0
}

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

MouseGetCell()
{
	global
	mousegetpos, xm, ym
	cm := ""
	rm := ""
	loop, 9
		if (xm >= wBorder+pos[a_index] and xm < wBorder+pos[a_index+1])
	{
		cm := a_index
		break
	}
	loop, 9
		if (ym >= hTitleMenu+pos[a_index] and ym < hTitleMenu+pos[a_index+1])
	{
		rm := a_index
		break
	}
	if (cm != "" and rm != "")
		return 1
}

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

NextElements(index)
{
	global
	IfThenElement[index] := IfThenElement[index-1]
	IfThenBeforeElement[index] := IfThenBeforeElement[index-1]
	ExplBeforeElement[index] := ExplBeforeElement[index-1]
	QueuedEliminations[index] := QueuedEliminations[index-1]
}

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

NextPermutation(pattern, maximum)
{
	global
	; The patterns array started with the lower numbers, and each pattern is sorted numerically.
	; => Permutating is done by increasing numbers, and the last numbers of each pattern are increased first.
	RevPattern := ""							; in reverse order ...
	loop, parse, pattern
		RevPattern := a_loopfield RevPattern
	FirstPossibleIncrease := ""
	loop, parse, RevPattern						; ... to loop higher numbers first
		if (a_loopfield < maximum-(a_index-1))  ; For a possible increase, the first digit of RevPattern (= the last digit of pattern) must be < maximum, the second digit must be < maximum-1 etc.
	{
		FirstPossibleIncrease := StrLen(pattern)-(a_index-1)  ; refers to pattern again, not to RevPattern
		break
	}
	if (FirstPossibleIncrease = "")
		return ""  ; => exit the loop for permutations
	permutation := ""
	loop, parse, pattern
	{
		if (a_index < FirstPossibleIncrease)
			permutation .= a_loopfield
		else if (a_index = FirstPossibleIncrease)
		{
			increase := a_loopfield+1
			permutation .= increase
		}
		else
		{
			increase += 1
			permutation .= increase
		}
	}
	return permutation
}

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

PencilMark(rc, p, value, color := "")
{
	global
	if not KeepLastAction
		LastAction := ""
	PencilMark[rc p] := value
	if (color != "")
		cPencilMark[rc p] := color
	if (value = 0)
		text("PencilMark" rc p, "")
	else if (value = 1)
	{
		if (display = "colors")
		{
			font("PencilMark" rc p, ColorValue[ColorName[p]], sPencilColor, "Webdings")
			text("PencilMark" rc p, "=")
		}
		else
		{
			if (highlight[rc] != "" and cPencilMark[rc p] = "default")
				color := "black"
			else if (cPencilMark[rc p] = "default")
				color := cDefault
			else
				color := cPencilMark[rc p]
			weight := cPencilMark[rc p]="default" ? 400 : 700
			font("PencilMark" rc p, color, sPencilNumber, "Ubuntu", "Arial", weight)
			text("PencilMark" rc p, p)
		}
	}
}

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

PleaseWait:
wingetpos, winx, winy, winwidth, winheight, %WinTitle% ahk_class AutoHotkeyGUI
x := winx+winwidth//2-2*wWhiteSquare
y := winy+winheight//2-wWhiteSquare//2
s := sNumber*3//5
gui, 1:+disabled
gui, PleaseWait:+owner1 -caption
gui, PleaseWait:font, s%s%
gui, PleaseWait:add, text,, Please wait ...
gui, PleaseWait:show, x%x% y%y% NoActivate
return

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

PossibleNumbersLen(cell1, cell2, cell3 := "")
{
	global
	PossibleNumbersString := ""
	loop, 3
		if (cell%a_index% != "")
			loop, parse, % PossibleNumbers[cell%a_index%]
				if not Instr(PossibleNumbersString, a_loopfield)
					PossibleNumbersString .= a_loopfield "-"
	PossibleNumbersLen := StrLen(PossibleNumbersString)/2
	if (PossibleNumbersLen = 3)
	{
		sort, PossibleNumbersString, d-
		n1 := SubStr(PossibleNumbersString, 1, 1)
		n2 := SubStr(PossibleNumbersString, 3, 1)
		n3 := SubStr(PossibleNumbersString, 5, 1)
	}
	return PossibleNumbersLen
}

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

set(rc, n, color := "")
{
	global
	if not KeepLastAction
		LastAction := ""
	if (KeepExplanationAndHighlightings != "" and not InStr(KeepExplanationAndHighlightings, rc n))
	{
		tooltip
		gui, explain:destroy
		loop, parse, AllCells, -
			highlight(a_loopfield, "")
		KeepExplanationAndHighlightings := ""
	}
	number[rc] := n
	if (color != "")
		cNumber[rc] := color
	if (context = "CreateImage" and not InStr(image, rc))
		return
	if (context = "EasyAndSymmetrical" or context = "DifficultButNotSymmetrical")
		return
	if (n = 0)
		text("number" rc, "")
	else
	{
		loop, 9
			PencilMark(rc, a_index, 0)
		if (display = "colors")
		{
			font("number" rc, ColorValue[ColorName[n]], sColor, "Webdings")
			text("number" rc, "=")
		}
		else
		{
			if (highlight[rc] != "")
				color := "black"
			else if (cNumber[rc] = "default")
			{
				if InStr(FixedCells, rc)
					color := cFixed
				else
					color := cDefault
			}
			else
				color := cNumber[rc]
			font("number" rc, color, sNumber, "Ubuntu", "Arial")
			text("number" rc, n)
		}
	}
}

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

SizeAndPosition(plus := 0)
{
	global
	size := {"wWhiteSquare": 80, "sHighlight": 60, "sGreySquare": 64, "sNumber": 50, "sColor": 54
	, "wPencilMark": 22, "sPencilNumber": 14, "sPencilColor": 12, "wCaption": 32, "sCaption": 20}
	ZoomMax := round(a_screenheight/960, 2)  ; 12 squares should not be higher than the screen.
	if (plus = 0 and wGui != "" or plus < 0 and zoom < 0.52 or plus > 0 and zoom > ZoomMax-0.02)
		return 0
	if (zoom+plus < 0.48)
		zoom := 0.48
	else if (zoom+plus > ZoomMax+0.02)
		zoom := ZoomMax+0.02
	else
		zoom += plus
	for var, value in size
		%var% := floor(zoom*value)
	if (a_screendpi != 96)
	{
		sHighlight := sHighlight*96//a_screendpi
		sGreySquare := sGreySquare*96//a_screendpi
		sNumber := sNumber*96//a_screendpi
		sColor := sColor*96//a_screendpi
		sPencilNumber := sPencilNumber*96//a_screendpi
		sPencilColor := sPencilColor*96//a_screendpi
		sCaption := sCaption*96//a_screendpi
	}
	if (zoom < 1)
		wLine := 1
	else if (zoom < 1.5)
		wLine := 2
	else if (zoom < 2)
		wLine := 3
	else
		wLine := 4
	pos := []
	loop, 10
	{
		pos[a_index] := (a_index-1)*(wWhiteSquare+wLine)  ; positions of rows/columns/captions relative to the gui
		if (a_index = 10)
			pos[a_index] += 3*wLine
		else if (a_index > 6)
			pos[a_index] += 2*wLine
		else if (a_index > 3)
			pos[a_index] += wLine
	}
	wGui := pos[10]+wCaption
	loop, 3
		add%a_index% := (wWhiteSquare-3*wPencilMark)//2 + (a_index-1)*wPencilMark  ; positions of pencil mark rows/columns relative to the cell
	xAdd := [add1, add2, add3, add1, add2, add3, add1, add2, add3]
	yAdd := [add1, add1, add1, add2, add2, add2, add3, add3, add3]
	loop, parse, AllCells, -
	{
		; The backgroundtrans option in the number and PencilMark text controls can cause double numbers/pencil marks.
		; Movedraw after Set() and PencilMark() to avoid this.
		if (number[a_loopfield] != 0)
			set(a_loopfield, number[a_loopfield])
		else loop, 9
			if (PencilMark[a_loopfield a_index] = 1)
				PencilMark(a_loopfield, a_index, 1)
		if (highlight[a_loopfield] != "")
			highlight(a_loopfield, highlight[a_loopfield], 1)
		r := SubStr(a_loopfield, 1, 1)
		c := SubStr(a_loopfield, 2, 1)
		font("GreySquare" a_loopfield, "0x818181", sGreySquare, "Webdings")
		guicontrol, 1:movedraw, GreySquare%a_loopfield%, % "x" pos[c] "y" pos[r] "w" pos[c+1]-pos[c] "h" pos[r+1]-pos[r]
		; GreySquare is not always an exact square.
		guicontrol, 1:movedraw, WhiteSquare%a_loopfield%, % "x" pos[c] "y" pos[r] "w" wWhiteSquare "h" wWhiteSquare
		guicontrol, 1:movedraw, number%a_loopfield%, % "x" pos[c] "y" pos[r] "w" wWhiteSquare "h" wWhiteSquare
		loop, 9
			guicontrol, 1:movedraw, PencilMark%a_loopfield%%a_index%, % "x" pos[c]+xAdd[a_index] "y" pos[r]+yAdd[a_index] "w" wPencilMark "h" wPencilMark
	}
	loop, parse, caption123, -
		guicontrol, 1:movedraw, caption123%a_loopfield%, % "x" pos[10] "y" pos[a_index]+wWhiteSquare*0.3 "w" wCaption "h" wWhiteSquare
	loop, parse, captionABC, -
		guicontrol, 1:movedraw, captionABC%a_loopfield%, % "x" pos[a_index] "y" pos[10] "w" wWhiteSquare "h" wCaption
	captions(cDefault)
	return 1
}

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

StringToGui(string)
{
	local rc
	string1 := ""
	loop, parse, AllCells, -
		string1 .= a_loopfield number[a_loopfield] "-"
	loop, parse, AllCells, -
	{
		number[a_loopfield] := 0
		cNumber[a_loopfield] := "default"
		loop, 9
		{
			PencilMark[a_loopfield a_index] := 0
			cPencilMark[a_loopfield a_index] := "default"
		}
	}
	loop, 5
		SubStr%a_index% := ""
	loop, parse, string, /, `n`r
		SubStr%a_index% := a_loopfield
	if (display != SubStr1)
	{
		menu, ViewMenu, rename, 5&, S&witch from %SubStr1% to %display%
		display := SubStr1
	}
	if (cBackground != SubStr2)
		background(SubStr2)
	loop, parse, SubStr3, `,
	{
		rc := SubStr(a_loopfield, 1, 2)
		number[rc] := SubStr(a_loopfield, 3, 1)
		if (StrLen(a_loopfield) > 3)
			cNumber[rc] := SubStr(a_loopfield, 4)
	}
	loop, parse, SubStr4, `,
	{
		rc := SubStr(a_loopfield, 1, 2)
		p := SubStr(a_loopfield, 3, 1)
		PencilMark[rc p] := 1
		if (StrLen(a_loopfield) > 3)
			cPencilMark[rc p] := SubStr(a_loopfield, 4)
	}
	if (FixedCells = "" and SubStr5 != "")
	{
		menu, SudokuMenu, rename, 5&, &Unfix
		menu, SudokuMenu, add, &Unfix, unfix
	}
	else if (FixedCells != "" and SubStr5 = "")
	{
		menu, SudokuMenu, rename, 5&, &Fix
		menu, SudokuMenu, add, &Fix, fix
	}
	FixedCells := SubStr5
	loop, parse, AllCells, -
	{
		set(a_loopfield, number[a_loopfield], cNumber[a_loopfield])
		if (number[a_loopfield] = 0)
			loop, 9
				PencilMark(a_loopfield, a_index, PencilMark[a_loopfield a_index], cPencilMark[a_loopfield a_index])
	}
	changes := 0
	loop, parse, AllCells, -
		if not InStr(string1, a_loopfield number[a_loopfield])
	{
		changes += 1
		if (changes > 1)
		{
			tooltip
			gui, explain:destroy
			loop, parse, AllCells, -
				highlight(a_loopfield, "")
			break
		}
	}
}

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

text(TextControl, text)
{
	global
	if (tTextControl[TextControl] != text or HighlightOnTop)
	{
		guicontrol, 1:text, %TextControl%, %text%
		tTextControl[TextControl] := text
	}
}
Last edited by pekoe on 28 Mar 2024, 15:46, edited 369 times in total.
User avatar
oldbrother
Posts: 273
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: 204
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: 204
Joined: 24 Jun 2015, 23:38

Re: Sudoku

29 Mar 2016, 01:02

Ok that works
joefiesta
Posts: 494
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: 494
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: 494
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 (v1)”

Who is online

Users browsing this forum: No registered users and 155 guests