Labürinte on mitmesuguste omadustega; järgnevas teeme programmi labürindi genereerimiseks, mis:
- katab N x N ruudustiku; ridade/veergude ruudud on indekseeritud 0..N, sissepääs on ruudus [0,0] - väljapääs - ruudus [N,N];
- ei sisalda ligipääsmatuid ruute;
- labürindi igast ruudust pääseb igasse teise ruutu täpselt üht (korduvate ruutudeta) teed mööda (kahe viimase omadusega
graafi nimetatakse kattepuuks).
Viimane omadus tagab, et sissepääsust [0,0] pääseb väljapääsu [N,N] parajasti üht (kordusteta) teed mööda.
Labürintide ja kattepuude genereerimiseks on esitatud palju algoritme, vaatleme järgnevas üht lihtsamatest, nn käikude uuristamise algoritmi:
- lisame igal sammul juba moodustatud labürindile (genereerimist alustades sisaldab labürint vaid ruudu [0,0]) ruudu, millesse pääseb mingist
juba moodustatud labürindiosa ruudust, kuid mis ise labürinti veel ei kuulu; jätkame, kuni kõik ruudustiku ruudud on lisatud labürindile.
On selge, et selline algoritm annab silmusteta labürindi, mis katab kõiki ruute, s.t. kattepuu.
Labürindi moodustamiseks kogume algul süsteemipredikaadiga findall kõik ruudud [X,Y] (0 <= X <= N) Käimata ruutude nimistuks ja eemaldame sealt ruudu [0,0], millest saab juba labürinti kuuluvate ruutude nimistu Käidud; labürindi kirjelduse saame nimistuna L kaarte paaridest [Ruut, Ruut1], kus Ruut on sellel sammul valitud juba labürinti kuuluv ruut ja Ruut1 - mingi tema naabruses (kõrval või üleval või all) olev ruut, mis seni labürinti veel ei kuulunud; eemaldame ruudu Ruut1 käimata ruutude nimistust ja jätkame, kuni käimata ruutude nimistu on tühi:
tee_labyr([],_,L,L,_):-!.
tee_labyr(Käimata,Käidud,L1,L,N):-
vali_ruut(Käidud,Ruut,Ruut1,N),
delete(Käimata,Ruut1,Käimata1),
tee_labyr(Käimata1, [Ruut1|Käidud],[[Ruut,Ruut1]|L1],L,N).
Et labürint tuleks võimalikult ühtlane, ilma pikkade otseteedeta, valime igal sammul võimalikult juhuslikult juba labürinti kuuluva ruudu, mille naabrusest hakkame otsima labürinti mittekuuluvat ruutu. Kuna selle ruudu valik võib ka ebaõnnestuda (valitud ruudu naabruses kuuluvad juba kõik ruudud labürinti), peab valikuprotseduur andma tagurdamisel erineva ruudu. Kahjuks annab Swi-Prologi sisseehitatud protseduur random ka tagurdamisel sama arvu, sellepärast ei kõlba sirgjooneline lahendus (Swi-Prologi sisseehitatud protseduur nth1(I, Nimistu, Element) valib nimistust Nimistu I-nda elemendi Element, kusjuures nimistu elementide indeksid algavad 1-st; kui indeksid algavad 0-st, peab kasutama protseduuri nth0):
Sellepärast peab uue ruudu valikul veidi nipitama: moodustame nimistu veel käimata ruutude nimistu indeksitest ja eemaldame sealt iga kord juba testitud indeksi; niimoodi saadakse tagurdamisel kindlasti alati uus ruut:
vali_ruut1(Käidud,Ruut,Ruut1,N,Indeksid,P):-
I is (random(P)+1), % 0 <= random(N) < N
((nth1(I,Käidud,Ruut),
naaber(Ruut,Ruut1,N),
not(member(Ruut1,Käidud)));
(delete(Indeksid,I,Indeksid1),P1 is P-1,P1>0,vali_ruut1(Käidud,Ruut,Ruut1,N,Indeksid1,P1))).
naaber([X,Y],[X,Y1],N):-
Y < N,
Y1 is Y+1.
naaber([X,Y],[X1,Y],_):-
X>0, X1 is X-1.
naaber([X,Y],[X,Y1],_):-
Y>0, Y1 is Y-1.
Labürindi joonistamisel läbitakse kõik ruudud rida-realt; igas ruudus [N1,M1] kontrollitakse, kas selle parempoolne serv ja selle alumine serv on kinni (tuleb joonistada sirglõik); rea viimases ruudus paremale loomulikult pole enam midagi tarvis joonistada ja viimases peas pole alla enam midagi joonistada; töö lõpeb, kui rea- ja veeruindeksid N1,M1 on mõlemad võrdsed N-ga:
joonista_labyr(_,_,_,_,_,N,N,N):-!.
joonista_labyr(L,P,Kylg,X0,Y0,N,N1,M1):-
N1<N,!, N2 is N1+1,
paremale(N1,N2,M1,L,P,X0,Y0,Kylg),
(M1<N,!,M2 is M1+1,alla(N1,M1,M2,L,P,X0,Y0,Kylg);true),
joonista_labyr(L,P,Kylg,X0,Y0,N,N2,M1).
joonista_labyr(L,P,Kylg,X0,Y0,N,N,M):-
M1 is M+1,
alla(N,M,M1,L,P,X0,Y0,Kylg),
joonista_labyr(L,P,Kylg,X0,Y0,N,0,M1).
paremale(N1,N2,M,L,P,X0,Y0,Kylg):-
not(member([[N1,M],[N2,M]],L)),
not(member([[N2,M],[N1,M]],L)),
X is X0+N2*Kylg,
Y1 is Y0+M*Kylg,
Y2 is Y1+Kylg,
send(P, display, new(_, line(X,Y1,X,Y2,none)));true.
alla(N1,M1,M2,L,P,X0,Y0,Kylg):-
not(member([[N1,M1],[N1,M2]],L)),
not(member([[N1,M2],[N1,M1]],L)),
X1 is X0+N1*Kylg,
X2 is X1+Kylg,
Y is Y0+(M1+1)*Kylg,
send(P, display, new(_, line(X1,Y,X2,Y,none)));true.
kyljed(Kylg,X0,Y0,N,P):-
%ylaX(X0),
X1 is X0+Kylg*(N+1),
%ylaY(Y0),
send(P, display, new(_, line(X0,Y0,X1,Y0,none))),
Y2 is Y0+Kylg*(N+1),
send(P, display, new(_, line(X0,Y2,X1,Y2,none))),
Y1 is Y0+Kylg,
send(P, display, new(_, line(X0,Y1,X0,Y2,none))),
Y11 is Y2-Kylg,
send(P, display, new(_, line(X1,Y0,X1,Y11,none))).
Algusruudust [0,0] lõppruutu [N,N] viiva silmusteta tee joonistamisel saab ära kasutada, et nimistusse L lisati ruudupaarid [Ruut, Ruut1] alati nii, et teisena on just lisatud ruut Ruut1, seega jõutakse lõppruuduni mingi paariga [[N1,M1],[N,N]]; kõik paarid, mis on lisatud veel pärast seda, võib kohe kõrvale jätta (nad ei saa kuuluda algusest lõppu viivalel kordusteta teele):
näita_tee([_|L],P,Kylg,X0,Y0,N):-
näita_tee(L,P,Kylg,X0,Y0,N).
hakka_joonistama([],_,_,_,_,_):-!.
hakka_joonistama([[[N1,M1],[N2,M2]]|L],Käidud,P,Kylg,X0,Y0):-
member([N2,M2],Käidud),not(member([N1,M1],Käidud)),
!,
X1 is X0+N1*Kylg+Kylg/2,
X2 is X0+N2*Kylg+Kylg/2,
Y1 is Y0+M1*Kylg+Kylg/2,
Y2 is Y0+M2*Kylg+Kylg/2,
send(P, display, new(@Lne, line(X1,Y1,X2,Y2,none))),
send(@Lne,colour,colour(red)),
send(@Lne,pen,2),
hakka_joonistama(L,[[N1,M1]|Käidud],P,Kylg,X0,Y0).
hakka_joonistama([_|L],Käidud,P,Kylg,X0,Y0):-
hakka_joonistama(L,Käidud,P,Kylg,X0,Y0).