Deprecated: Function set_magic_quotes_runtime() is deprecated in /DISK2/WWW/lokiware.info/mff/wakka.php on line 35
Loydova osmička je hlavolam s osmi čverečky v matici 3x3 a jedním prázdným místem. Cílem je uspořádat čtverečky do základní konfigurace (1, 2, 3; 4, 5, 6; 7, 8, prázdno). Normálně to je Loydova patnáctka, ale princip je stejný. Mimochodem, nevymyslel to Loyd,
více třeba na Wikipedii, ten na tom jen vydělal peníze.
Následuje Adamova implementace podle návodu ze cvičení, více v komentářích. Testováno jen ve FreePascalu?. Zkoušel jsem to ve starém TurboPascalu?, ale na ten jsou tam moc velká čísla a nelíbí se mu konstantní parametry, tak jsem se s tím raději dál nepatlal. Neměl by ale být problém to upravit. Možná se na vás bude Borland ještě zlobit za to, že ignorujete návratové hodnoty funkce (CQAddItem() vrací boolean, ale já na to kašlu, protože vím, že je to vždy TRUE), v tom případě to přiřaďte do nějaké odpadní booleovské proměnné.
program LoydovaOsmicka(input, output); { nebo trojka:), 15ka je na nas moc, FPC si moc nerozumi s 64 bity:( }
{
Pouzivam kruhovou frontu (viz CodeQueue), staci pak radove nizsi pamet (resp. nemusim porad dokola sesypavat).
Pole indexuju od nuly a prazdne misto mam take oznacene nulou. Jinak je to presne podle navodu ze cviceni.
Komentare odtud dal a identifikatory jsou anglicky. Pisu tak vzdycky, prijde mi to lepsi nez mixovat jazyky
a prznit cestinu.
Vsechna cisla mimo 1 a 0 najdete mezi konstantami na zacatku, Side muzete zmenit na 2 a odpovidajicim zpusobem
dalsi cisla, viz nize.
Procedura PermMove() by mohla byt rychlejsi, to by ale bylo zbytecne, protoze ji pouzivam jen pro rekonstrukci
tahu pri vypisu reseni. Takhle je aspon srozumitelne (snad) a kratce zapsana.
Program umi jednak resit resitelne konfigurace, jednak vypsat pocet kroku nutnych pro reseni vsech
konfiguraci. Za povsimnuti stoji, ze algoritmus je v podstate totozny a ze (tim padem) vyreseni netrivialni
konfigurace trva radove stejne dlouho jako nalezeni poctu kroku pro vsechny konfigurace.
Pokud zvolite druhou moznost, vypisou se take dve konkretni konfigurace, pro ktere je reseni nejdelsi
(StepsMax=31 kroku), a vypise se i pocet konfiguraci, pro ktere reseni neexistuje, muzete si overit,
ze je to presne polovina z celkoveho poctu (PermCodeMax+1), coz je spravne. Kazda konfigurace totiz patri
do prave jedne poloviny konfiguraci, ktere ze sebe navzajem lze ziskat soupanim ctverecku.
}
const { You change Size to 2 (four is too much:(), if you also change StepsMax to 6 and PermCodeMax to 23. }
Side = 3;
Count = Side*Side;
Max = Count-1;
NghbrMax = 3; { There are up to four neighbors per square. }
NghbrOffsets : array[0..NghbrMax] of integer = (-Side, 1, Side, -1);
PermCodeMax = 362879 {23}; { = fact(Side*Side)-1 }
CQMax = 24058; { Max queue size-1. Empirically determined:) (for 3x3). }
StepsMax = 31 {6}; { How many steps needed to solve a config at most. }
SqEmpty = 0;
type
SqNumber = SqEmpty..Max; { square number }
Perm = array[0..Max] of SqNumber;
PermCode = longint;
PermCodeNghbrs = record
count : integer;
codes : array[0..NghbrMax] of PermCode;
moves : array[0..NghbrMax] of SqNumber;
end;
CodeQueue = record
buf : array[0..CQMax] of PermCode;
head : integer; { place for newly added items }
tail : integer; { the "oldest" item }
isEmpty : boolean;
end;
function PermToCode(const p : Perm): PermCode; forward;
procedure CodeToPerm(c: PermCode; var p: Perm); forward;
procedure CodeGetNeighbors(c: PermCode; var neighbors : PermCodeNghbrs); forward;
function PermEmptyIndex(const p: Perm) : integer; forward;
procedure PermMove(var p: Perm; num : SqNumber); forward;
procedure WriteSquarePerm(p: Perm); forward;
procedure ReadSquarePerm(var p: Perm); forward;
procedure CountSteps; forward;
procedure SolveConfig; forward;
{ CodeQueue }
procedure CQMakeEmpty(var buf : CodeQueue); forward;
function CQIsFull(const buf : CodeQueue) : boolean; forward;
function CQAddItem(var buf : CodeQueue; item : PermCode) : boolean; forward;
function CQGetItem(var buf : CodeQueue; var item : PermCode) : boolean; forward;
function PermToCode(const p : Perm): PermCode;
var
i : integer;
j : integer;
used : array[0..Max] of boolean;
repeatCount : longint; { How many times in a row the same number appears at a certain position }
c : PermCode;
begin
for i := 0 to Max do
used[i] := FALSE;
c := 0;
repeatCount := 1;
for i :=2 to Max do
repeatCount := repeatCount * i;
for i := 0 to Max-1 do
begin
for j := 0 to p[i]-1 do
begin
if not used[j] then
c := c + repeatCount;
end;
used[p[i]] := TRUE;
repeatCount := repeatCount div (Max-i);
end;
PermToCode := c;
end;
procedure CodeToPerm(c: PermCode; var p: Perm);
var
i : integer;
j : integer;
k : integer;
n : integer;
used : array[0..Max] of boolean;
repeatCount : longint;
begin
for i := 0 to Max do
used[i] := FALSE;
repeatCount := 1;
for i :=2 to Max do
repeatCount := repeatCount * i;
for i := 0 to Max do
begin
n := c div repeatCount;
c := c mod repeatCount;
j := 0;
k := 0;
while j <= n do
begin
if not used[k] then
inc(j);
inc(k);
end;
dec(k);
p[i] := k;
if i <> Max then
begin
used[k] := TRUE;
repeatCount := repeatCount div (Max-i);
end;
end;
end;
function PermEmptyIndex(const p: Perm) : integer;
var
i : integer;
begin
i := 0;
while p[i] <> SqEmpty do
inc(i);
PermEmptyIndex := i;
end;
procedure CodeGetNeighbors(c: PermCode; var neighbors : PermCodeNghbrs);
var
p : Perm;
emptyIdx : integer;
nghbrIdx : integer;
i : integer;
begin
neighbors.count := 0;
CodeToPerm(c, p);
emptyIdx := PermEmptyIndex(p);
for i := 0 to NghbrMax do
begin
nghbrIdx := emptyIdx + NghbrOffsets[i];
if (nghbrIdx>=0) and (nghbrIdx<=Max) and { within bounds }
(((nghbrIdx div Side) = (emptyIdx div Side)) or
((nghbrIdx mod Side) = (emptyIdx mod Side)) { in the same row or column }
) then
begin
p[emptyIdx] := p[nghbrIdx];
p[nghbrIdx] := SqEmpty;
neighbors.codes[neighbors.count] := PermToCode(p);
neighbors.moves[neighbors.count] := p[emptyIdx];
inc( neighbors.count );
p[nghbrIdx] := p[emptyIdx];
p[emptyIdx] := SqEmpty;
end;
end;
end;
procedure PermMove(var p: Perm; num : SqNumber);
var
emptyIdx : integer;
i : integer;
n : PermCodeNghbrs;
begin
emptyIdx := PermEmptyIndex(p);
CodeGetNeighbors(PermToCode(p), n);
i := 0;
repeat
CodeToPerm(n.codes[i], p);
inc(i);
until (p[emptyIdx] = num) ; { or (i = n.count) - just for safety, not needed for valid args }
end;
procedure WriteSquarePerm(p: Perm);
var
i : integer;
j : integer;
begin
for i := 0 to Side-1 do
begin
for j := 0 to Side-1 do
begin
if p[ i*Side + j ] = SqEmpty then
write(' _')
else
write(' ', p[ i*Side + j ]); { works well for 1-digit numbers }
end;
writeln;
end;
end;
procedure ReadSquarePerm(var p: Perm);
var
i : integer;
j : integer;
begin
for i := 0 to Side-1 do
begin
write(' row ',i, '> ');
for j := 0 to Side-1 do
read(p[ i*Side + j ]);
readln;
end;
end;
{CodeQueue}
procedure CQMakeEmpty(var buf : CodeQueue);
begin
buf.head := 0;
buf.tail := 0;
buf.isEmpty := TRUE;
end;
function CQIsFull(const buf : CodeQueue) : boolean;
begin
CQIsFull := (buf.head = buf.tail) and not buf.isEmpty;
end;
function CQAddItem(var buf : CodeQueue; item : PermCode) : boolean;
begin
if not CQIsFull(buf) then
begin
buf.buf[buf.head] := item;
buf.head := (buf.head+1) mod CQMax;
if buf.isEmpty then
buf.isEmpty := FALSE;
CQAddItem := TRUE;
end
else
CQAddItem := FALSE;
end;
function CQGetItem(var buf : CodeQueue; var item : PermCode) : boolean;
begin
if not buf.isEmpty then
begin
item := buf.buf[buf.tail];
buf.tail := (buf.tail+1) mod CQMax;
if buf.tail = buf.head then
buf.isEmpty := TRUE;
CQGetItem := TRUE;
end
else
CQGetItem := FALSE;
end;
procedure CountSteps;
var
neighbors : PermCodeNghbrs;
i : longint;
p : Perm;
c1 : PermCode;
c : PermCode;
queue : CodeQueue;
codeSteps : array[0..PermCodeMax] of -1..StepsMax;
stepStats : array[-1..StepsMax] of longint;
begin
for i := 0 to PermCodeMax do
codeSteps[i] := -1;
stepStats[-1] := PermCodeMax; { target config (0 steps) not included }
for i := 0 to StepsMax do
stepStats[i] := 0;
{ Target config }
for i:=1 to Max do
p[i-1] := i;
p[Max] := SqEmpty;
c1 := PermToCode( p );
CQMakeEmpty(queue);
CQAddItem(queue, c1);
codeSteps[c1] := 0;
inc(stepStats[0]);
while CQGetItem(queue, c) do
begin
CodeGetNeighbors(c, neighbors);
for i:=0 to neighbors.count-1 do
begin
if codeSteps[ neighbors.codes[i] ] = -1 then
begin
codeSteps[ neighbors.codes[i] ] := codeSteps[c]+1;
inc( stepStats[ codeSteps[ neighbors.codes[i] ] ] );
dec( stepStats[-1] );
if codeSteps[ neighbors.codes[i] ] = StepsMax then
begin
writeln(codeSteps[ neighbors.codes[i] ], ' steps:');
CodeToPerm(neighbors.codes[i], p);
WriteSquarePerm(p);
end;
CQAddItem(queue, neighbors.codes[i]);
end
end;
end;
writeln(stepStats[-1], ' configurations are unsolvable.');
for i := 0 to StepsMax do
writeln(stepStats[i], ' configurations are solvable in ', i, ' steps.');
end;
procedure SolveConfig;
var
neighbors : PermCodeNghbrs;
i : longint;
p : Perm;
c0 : PermCode;
c1 : PermCode;
c : PermCode;
queue : CodeQueue;
codeMoves : array[0..PermCodeMax] of SqNumber;
begin
for i := 0 to PermCodeMax do
codeMoves[i] := SqEmpty;
{ Initial config }
writeln('Enter the initial configuration:');
ReadSquarePerm( p );
c0 := PermToCode( p );
writeln;
{ Target config }
for i:=1 to Max do
p[i-1] := i;
p[Max] := SqEmpty;
c1 := PermToCode( p );
CQMakeEmpty(queue);
CQAddItem(queue, c1);
while CQGetItem(queue, c) and (codeMoves[c0] = SqEmpty) do
begin
CodeGetNeighbors(c, neighbors);
for i:=0 to neighbors.count-1 do
begin
if codeMoves[ neighbors.codes[i] ] = SqEmpty then
begin
codeMoves[ neighbors.codes[i] ] := neighbors.moves[i];
CQAddItem(queue, neighbors.codes[i]);
end
end;
end;
if codeMoves[c0] = SqEmpty then
writeln('Unsolvable.')
else
begin
writeln('Solution:');
c := c0;
CodeToPerm(c, p);
i := 1;
repeat
writeln('#', i, ' Move ', codeMoves[c], ':');
PermMove(p, codeMoves[c] );
WriteSquarePerm(p);
c := PermToCode(p);
inc(i);
until c = c1;
end;
end;
var
opt : integer;
begin
writeln('Choose an option (enter one digit):');
writeln(' [1] Solve a configuration.');
writeln(' [2] Compute steps needed to solve all configurations.');
writeln;
readln(opt);
if opt = 1 then
SolveConfig
else if opt = 2 then
CountSteps
else
writeln('Unknown option.');
end.