Készítsünk megoldót a Katamino-feladványokhoz!
A feladat mindig az, hogy a 12 pentominó egy adott részhalmazából rakjunk ki egy 5xn-es téglalapot.
Az első lépés továbbra is az, hogy valahogyan le kell írni a gép számára az adatokat, vagyis a használható alakzatokat. Sokféle leírás elképzelhető; itt most csináljuk a következőt: minden egyes alakzatot a körülírható téglalap bal alsó sarkához képest számolt koordinátáival írjuk le.
Például a T-alakzatnál:
. . O
O O O
. . O
Ha a bal alsó sarok a (0,0) pont, akkor az alakzathoz tartozó pontok a következők: (0,1), (1,1), (2,0), (2,1) és (2,2). A pontokat x-koordináta szerint, és azon belül y-koordináta szerint rendezve írjuk fel.
Az x és y-koordináták összefogására bármilyen struktúrát használhatunk, pl. p(0,1)
, de a tömörség kedvéért szokás a -
operátort alkalmazni: 0-1
. Bár az egyes forgatások/tükrözések programból is legenerálhatóak, egyszerűbb mindegyiket külön adatként megadni.
A T esetében pl.:
, [0-0,0-1,0-2,1-1,2-1]).
alakzat(t, [0-0,1-0,1-1,1-2,2-0]).
alakzat(t, [0-1,1-1,2-0,2-1,2-2]).
alakzat(t, [0-2,1-0,1-1,1-2,2-2]). alakzat(t
Hasonlóan lehet definiálni a többi alakzatot, amelyek mind valamilyen 1 betűből álló kódot kaptak: k
, p
, c
stb. (Az összes alakzat, a teljes program forráskódjával együtt, a fájl végén található.)
A programunk fő szabálya a kirak
lesz, ami egy alakzat-listához megadja, hogy hogyan fog kinézni a tábla. A megoldás módszere nagyon egyszerű: mindig a (balról/alulról) első lyukat próbáljuk betömni.
Ak, X) :- hossz(Ak, N), kirak(Ak, N, [], X).
kirak(
, _, T, T).
kirak([]Ak, N, T, X) :-
kirak(N, T, P),
első_lyuk(A, Ak, Ak1),
töröl(A, P, N, T, T1),
letesz(Ak1, N, T1, X). kirak(
A 2-argumentumú változat megnézi, hogy hány alakzatot kapott (tehát milyen hosszú a tábla), és meghívja a 4-argumentumú testvérét. Ez a felhasználható alakzatokon (Ak
) kívül még megkapja a tábla hosszát (N
), a tábla jelenlegi állapotát (T
), és ezáltal kiszámolja a kitöltött táblát (X
).
Ehhez megkeresi az első lyukat, majd kiválaszt (töröl) egy alakzatot, azt leteszi úgy, hogy lefedje a lyukat, és rekurzívan folytatja a műveletet, amíg mindent le nem rak.
A tábla állapotát egy listában tároljuk, aminek az elemei h(X-Y,A)
alakban adják meg, hogy az X-Y
pozíción az A
alakzat egy darabja található. Az első lyuk megkeresése így nagyon egyszerű:
N, T, X-Y) :-
első_lyuk(1, N, X), között(1, 5, Y),
között(\+ tartalmaz(h(X-Y,_), T), !.
Ebből a között
feladatként fel volt adva korábban:
N, M, N) :- N =< M.
között(N, M, X) :- N < M, N1 is N + 1, között(N1, M, X). között(
Visszatérve az első_lyuk
-ra, ez deklaratív olvasatban azt mondja, hogy az X-Y
koordináták a megfelelő keretek között vannak, és a tábla ezen a pozíción nem tartalmaz elemet. Mitől fogja ez az elsőt adni? Azért, mert a procedurális olvasatból tudjuk, hogy sorban fog próbálkozni, tehát először az X = 1
eseteket próbálja végig különböző Y
értékekre, aztán az X = 2
-t stb., és a végén levő vágásnak köszönhetően nem fog további lyukakat megadni akkor sem, ha a keresés visszalépne ide.
Már csak a letesz(A, X-Y, N, T, T1)
szabály van hátra. Ez megpróbálja az A
alakzatot lerakni úgy, hogy lefedje az X-Y
pontot, ne menjen ki az 5xN-es táblából, és ne takarjon olyan pozíciókat, amelyek szerepelnek T
-ben. Ha sikerül, akkor az alakzat lehelyezésével kapott új tábla a T1
.
A, X-Y, N, T, T1) :-
letesz(A, [_-Dy|Dk]),
alakzat(Y1 is Y - Dy, Y1 > 0,
A, X-Y1, Dk, N, [h(X-Y,A)|T], T1). letesz(
Ez tehát az A
alakzatnak kiválasztja egy forgatását, és megnézi az első pontjának az y-koordinátáját (Dy
). Az Y
koordinátánál ennyivel lejjebb kell rakni az alakzatot, hogy az első pont fedje a lyukat (hiszen az első pont az alakzat legbaloldalibb, és azon belül legalsó pontja). Ha ez a módosult Y1
koordináta nem pozitív, akkor az alakzat kilóg.
A tényleges lerakási kísérletet a letesz
6-argumentumú változata végzi:
_, _, [], _, T, T).
letesz(A, X-Y, [Dx-Dy|Dk], N, T, T1) :-
letesz(X1 is X + Dx, között(1, N, X1),
Y1 is Y + Dy, között(1, 5, Y1),
\+ tartalmaz(h(X1-Y1,_), T),
A, X-Y, Dk, N, [h(X1-Y1,A)|T], T1). letesz(
Ez plusz argumentumként megkapja a kiválasztott forgatáshoz tartozó pontokat is (az első kivételéve, amivel a lyukat fedtük), és ezeken megy végig rekurzívan. Minden lépésben a (módosított) X-Y
koordináta alapján kiszámolja, hogy hova kerül a pont, és ellenőrzi, hogy értelmes-e a koordináta és nincs-e már lefedve T
-ben.
A lényeggel ugyan már készen vagyunk, de az eredmény nehezen olvasható:
?- kirak([l,t,w,k,y,r,z,c,p], X).
X = [h(9-5, c), h(9-4, c), h(9-3, c), h(8-5, c), h(8-3, c),
h(9-2, p), h(9-1, p), h(8-2, p), h(8-1, p), h(7-1, p),
h(8-4, z), h(7-4, z), h(7-3, z), h(7-2, z), h(6-2, z),
h(7-5, r), h(6-5, r), h(6-4, r), h(6-3, r), h(5-4, r),
h(5-5, w), h(4-5, w), h(4-4, w), h(3-4, w), h(3-3, w),
h(6-1, y), h(5-2, y), h(5-1, y), h(4-1, y), h(3-1, y),
h(5-3, k), h(4-3, k), h(4-2, k), h(3-2, k), h(2-2, k),
h(3-5, t), h(2-5, t), h(2-4, t), h(2-3, t), h(1-5, t),
h(2-1, l), h(1-4, l), h(1-3, l), h(1-2, l), h(1-1, l)]
Próbáljuk meg kiíratni egy kicsit “grafikusabb” formában! Az elsődleges szabályunk akkor ez lesz:
Ak) :- hossz(Ak, N), kirak(Ak, X), kiír(N, X). katamino(
A kiír(N, T)
pedig 5 sorba rendezve szépen kiírja az N
hosszú T
táblát. A kiíráshoz a write(X)
kifejezést fogjuk használni, ami kiírja az X
értékét, új sor nyitásához pedig a nl
-t (newline, újsor).
N, T) :- kiír(N, 1-5, T).
kiír(
_, _-0, _).
kiír(N, X-Y, T) :- Y =< 5, X > N, Y1 is Y - 1, nl, kiír(N, 1-Y1, T).
kiír(N, X-Y, T) :-
kiír(Y =< 5, X =< N,
X-Y,A), T),
tartalmaz(h(write(A), write(' '),
X1 is X + 1,
N, X1-Y, T). kiír(
A 2-argumentumú változat csak átadja a feladatot a 3-argumentumúnak, ami megkapja az éppen kiírandó elem X-Y
koordinátáját is. Ennek három szabálya van:
X > N
, akkor vége az aktuális sornak, új sort kezdünk. Az Y
koordináta felülről megy lefelé, mert a kiírás is felülről lefelé történik.X-Y
pozíción melyik alakzat található, és kiírja, utána kiír még egy szóközt, és továbbmegy a következő X
pozícióra.Nézzük meg!
?- katamino([l,t,w,k,y,r,z,c,p]).
t t t w w r r c c
l t w w r r z z c
l t w k k r z c c
l k k k y z z p p
l l y y y y p p p
true
És ez éppen az a megoldás, ami a képen van! Erre persze elég jó esély volt, ugyanis a felhasználandó alakzatok listáját hozzávetőlegesen a képen látható megoldás sorrendjében adtuk meg. Egy másik permutáció más megoldást talál meg először:
?- katamino([t,w,l,k,r,z,y,p,c]).
w y y y y l l l l
w w y p p p r r l
t w w p p z z r r
t t t k k z c r c
t k k k z z c c c
true
A Katamino szabályai szerint a kirakandó téglalap magassága mindig 5. Oldjuk fel ezt a korlátot! Írjátok át a programot úgy, hogy a katamino
szabály paraméterben kapja meg a magasságot is, és ennek megfelelő megoldást keressen! A program vegye észre rögtön, ha a magasság nem illik a megkapott alakzatok számához (pl. 10 alakzat és 7-es magasság). Keressetek megoldást a 3x20, 4x15, 5x12, 6x10 téglalapok kitöltésére! (Tipp: a szabályokban az eddigi N
paramétert cseréljétek N-M
párra.)
Ak) :- hossz(Ak, N), kirak(Ak, X), kiír(N, X).
katamino(
% Megoldó
Ak, X) :- hossz(Ak, N), kirak(Ak, N, [], X).
kirak(
, _, T, T).
kirak([]Ak, N, T, X) :-
kirak(N, T, P),
első_lyuk(A, Ak, Ak1),
töröl(A, P, N, T, T1),
letesz(Ak1, N, T1, X).
kirak(
N, T, X-Y) :-
első_lyuk(1, N, X), között(1, 5, Y),
között(\+ tartalmaz(h(X-Y,_), T), !.
A, X-Y, N, T, T1) :-
letesz(A, [_-Dy|Dk]),
alakzat(Y1 is Y - Dy, Y1 > 0,
A, X-Y1, Dk, N, [h(X-Y,A)|T], T1).
letesz(
_, _, [], _, T, T).
letesz(A, X-Y, [Dx-Dy|Dk], N, T, T1) :-
letesz(X1 is X + Dx, között(1, N, X1),
Y1 is Y + Dy, között(1, 5, Y1),
\+ tartalmaz(h(X1-Y1,_), T),
A, X-Y, Dk, N, [h(X1-Y1,A)|T], T1).
letesz(
% Kiírás
N, T) :- kiír(N, 1-5, T).
kiír(
_, _-0, _).
kiír(N, X-Y, T) :- Y =< 5, X > N, Y1 is Y - 1, nl, kiír(N, 1-Y1, T).
kiír(N, X-Y, T) :-
kiír(Y =< 5, X =< N,
X-Y,A), T),
tartalmaz(h(write(A), write(' '),
X1 is X + 1,
N, X1-Y, T).
kiír(
% Segéd-szabályok
X, [X|_]).
tartalmaz(X, [_|Maradék]) :- tartalmaz(X, Maradék).
tartalmaz(
X, [X|M], M).
töröl(X, [Y|M], [Y|M1]) :- töröl(X, M, M1).
töröl(
, 0).
hossz([]_|M], N) :- hossz(M, N1), N is 1 + N1.
hossz([
N, M, N) :- N =< M.
között(N, M, X) :- N < M, N1 is N + 1, között(N1, M, X).
között(
% Alakzatok
% kígyó (lila)
, [0-0,0-1,0-2,1-2,1-3]).
alakzat(k, [0-0,0-1,1-1,1-2,1-3]).
alakzat(k, [0-0,1-0,1-1,2-1,3-1]).
alakzat(k, [0-0,1-0,2-0,2-1,3-1]).
alakzat(k, [0-1,0-2,0-3,1-0,1-1]).
alakzat(k, [0-1,1-0,1-1,2-0,3-0]).
alakzat(k, [0-1,1-1,2-0,2-1,3-0]).
alakzat(k, [0-2,0-3,1-0,1-1,1-2]).
alakzat(k% P-betű (rózsaszín)
, [0-0,0-1,0-2,1-0,1-1]).
alakzat(p, [0-0,0-1,0-2,1-1,1-2]).
alakzat(p, [0-0,0-1,1-0,1-1,1-2]).
alakzat(p, [0-0,0-1,1-0,1-1,2-0]).
alakzat(p, [0-0,0-1,1-0,1-1,2-1]).
alakzat(p, [0-0,1-0,1-1,2-0,2-1]).
alakzat(p, [0-1,0-2,1-0,1-1,1-2]).
alakzat(p, [0-1,1-0,1-1,2-0,2-1]).
alakzat(p% C-betű (sárga)
, [0-0,0-1,0-2,1-0,1-2]).
alakzat(c, [0-0,0-1,1-0,2-0,2-1]).
alakzat(c, [0-0,0-1,1-1,2-0,2-1]).
alakzat(c, [0-0,0-2,1-0,1-1,1-2]).
alakzat(c% W-betű (világoszöld)
, [0-0,0-1,1-1,1-2,2-2]).
alakzat(w, [0-0,1-0,1-1,2-1,2-2]).
alakzat(w, [0-1,0-2,1-0,1-1,2-0]).
alakzat(w, [0-2,1-1,1-2,2-0,2-1]).
alakzat(w% L-betű (narancssárga)
, [0-0,0-1,0-2,0-3,1-0]).
alakzat(l, [0-0,0-1,0-2,0-3,1-3]).
alakzat(l, [0-0,0-1,1-0,2-0,3-0]).
alakzat(l, [0-0,0-1,1-1,2-1,3-1]).
alakzat(l, [0-0,1-0,1-1,1-2,1-3]).
alakzat(l, [0-0,1-0,2-0,3-0,3-1]).
alakzat(l, [0-1,1-1,2-1,3-0,3-1]).
alakzat(l, [0-3,1-0,1-1,1-2,1-3]).
alakzat(l% Y-betű vagy tengeralattjáró (barna)
, [0-0,0-1,0-2,0-3,1-1]).
alakzat(y, [0-0,0-1,0-2,0-3,1-2]).
alakzat(y, [0-0,1-0,1-1,2-0,3-0]).
alakzat(y, [0-0,1-0,2-0,2-1,3-0]).
alakzat(y, [0-1,1-0,1-1,1-2,1-3]).
alakzat(y, [0-1,1-0,1-1,2-1,3-1]).
alakzat(y, [0-1,1-1,2-0,2-1,3-1]).
alakzat(y, [0-2,1-0,1-1,1-2,1-3]).
alakzat(y% I-betű vagy egyenes (kék)
, [0-0,0-1,0-2,0-3,0-4]).
alakzat(i, [0-0,1-0,2-0,3-0,4-0]).
alakzat(i% r-betű (szürke)
, [0-0,0-1,1-1,1-2,2-1]).
alakzat(r, [0-0,1-0,1-1,1-2,2-1]).
alakzat(r, [0-1,0-2,1-0,1-1,2-1]).
alakzat(r, [0-1,1-0,1-1,1-2,2-0]).
alakzat(r, [0-1,1-0,1-1,1-2,2-2]).
alakzat(r, [0-1,1-0,1-1,2-1,2-2]).
alakzat(r, [0-1,1-1,1-2,2-0,2-1]).
alakzat(r, [0-2,1-0,1-1,1-2,2-1]).
alakzat(r% V-betű vagy sarok (kék)
, [0-0,0-1,0-2,1-0,2-0]).
alakzat(v, [0-0,0-1,0-2,1-2,2-2]).
alakzat(v, [0-0,1-0,2-0,2-1,2-2]).
alakzat(v, [0-2,1-2,2-0,2-1,2-2]).
alakzat(v% Z-betű vagy S-betű (kék)
, [0-0,0-1,1-1,2-1,2-2]).
alakzat(z, [0-0,1-0,1-1,1-2,2-2]).
alakzat(z, [0-1,0-2,1-1,2-0,2-1]).
alakzat(z, [0-2,1-0,1-1,1-2,2-0]).
alakzat(z% pluszjel (piros)
+, [0-1,1-0,1-1,1-2,2-1]).
alakzat(% T-betű (zöld)
, [0-0,0-1,0-2,1-1,2-1]).
alakzat(t, [0-0,1-0,1-1,1-2,2-0]).
alakzat(t, [0-1,1-1,2-0,2-1,2-2]).
alakzat(t, [0-2,1-0,1-1,1-2,2-2]). alakzat(t