Rubic 2D

Objektid, millel on palju sümmeetriaid, näiteks pööratavate üks-üheste teisenduste hulgad (rühmad) on meile väga raskesti mõistetavad ja hallatavad. Hea näide on Budapesti Rakenduskusti Akadeemia sisustusala lektori Erno Rubic-u poolt 1974 aastal leiutatud "Rubic'u kuup". Vaatamata näilisele lihtsusele on originaalsel 3x3x3 kuubil 43 252 0032 274 489 856 000 seisu (olekut) ja on arusaadav, et sellises võimaluste (sümmeetriate) hulgas orienteerumine muutus kuubi ilmumisel paljudele meeliköitvaks väljakutseks; praegu korraldatakse Rubic'u kuubi lahendamise maailmameistrivõislusi.

Koostame järgnevas Rubic-u kuubiga sarnaneva mängu (tasapinnalise), mis võimaldab mängijal kontrollida oma võimet aru saada sümmeetriatest.

Pilt, mida mängija siin näeb ("Rubicu ruut"), koosneb N x N värvilisest ruudukesest (XPCE elementaarobjektid box); loomulik on koostada see ühe liitobjektina (compound object, mitmest elementaarsest graafilisest objektist koostatud kujund). Liitobjektid on XPCE-s defineeritud klassis device; nende koostamine on täiesti analoogiline graafikaaknas elementaarobjektidest pildi koostamisega, erinevus on vaid selles, et kogu liitobjekt näidatakse graafikaaknas korraga, käsku show pole tarvis objekti komponentide jaoks anda.

Defineerime mängu akna ja selles näidatava ruudukestest koosneva liitobjekti globaalsete objektidena; et mängu saaks korduvalt käivitada, hävitame (igaks juhuks) enne objektide loomist (võibolla) eelnevalt mälus olnud vastava objekti. Rubic-u ruudu ridade-veergude arv on mängija antud parameeter, salvestame selle dünaamilise (s.t. assert-retract lausetes kasutatava ) predikaadiga dim/1; teine dünaamiline predikaat segatud salvestab käigud, mida Prolog kasutas juhusliku seisu loomisel:

:- use_module(library(pce)).
:- free(@aken).
:- pce_global(@aken, new(window('Rubic_2D'))).
:- free(@rubic).
:- pce_global(@rubic, new(device)).
:- dynamic dim/1, segatud/1.

Mäng algab akna avamise ja selles N x N Rubic-u ruudu loomisega:

rubic(N):-
retractall(dim(_)),assert(dim(N)),
send(@aken, size, size(N*32, N*32)), %aken on juba loodud!
make_board(N),
algasend,
send(@aken, display, @rubic),
send(@aken, open).

Mängija juhib mängu hiireklõpsudega:
- klõps vasakpoolse hiireklahviga - kõigi klõpsatud ruudu veerus ja reas asuvate ruudukeste värv suureneb ühe võrra (värvid on järjekorras must,valge,punane,roheline,sinine,kollane);
- klõps parempoolse hiireklahviga - kõigi klõpsatud ruudu veerus ja reas asuvate ruudukeste värv väheneb ühe võrra (eelmise pöördteisendus) - kõrvaloleval joonisel on seis, mis tekib algasendis parempoolse klahviga ruudul 2,1 klõpsamisel;
- topeltklõps vasakpoolsega - kõik algasendisse;
- topeltklõps vasakpoolsega + Shift: Prolog valib juhusliku arvu N, 1 < N < ja "segab" mängulaua, sooritades N juhuslikku käiku; mängija ülesandeks on võimalikult väikese arvu käikudega laud uuesti algseisu saada;
- klõps vasakpoolsega + Shift (kui laud on eelnevalt segatud) - Prolog teeb eelnevalt sooritatud segamise viimase operatsiooni pöördoperatsiooni, näidates ruudu piirjoone paksemaks muutumisega, millisele ruudule klõpsati; seda operatsiooni korrates võib mängija samm-sammult jõuda tagasi algasendini.

XPCE-s "reageerivad" hiiresündmustele vaid mõned elementaarobjektid (button, menu); et graafilisi objekte (Rubic-u ruudu värvilised ruudukesed - XPCE elementaarobjektid box) muutuks "hiiretundlikuks", peab neile liitma vastava sündmuse (XPCE terminoloogias gesture) tunnistaja, iga klõpsuliigi jaoks oma. Hiiresündmuste tunnistaja click_gesture argumendid on:
- millist hiire nuppu klõpsati (left, right, middle);
- milliseid modifikaatoreid (Shift, Control, Alt ) tuleb arvestada;
- milline klõps (single, double);
- milline peab olema reaktsioon, s.t. milline sõnum saata; kui reaktsiooniks on Prologi predikaadi käivitamine, on predikaadi message esimene argument sisseehitatud konstant @prolog, teine - käivitatav predikaat ja selle järel selle predikaadi argumendid, näiteks vasakpoolse hiirenupuga klõps käivitab predikaadi switch_colours, mille esimene argument on viit klõpsatud ruudule @Nme ja teine - 0. Predikaat make_board(N) loob N x N ruudukesest koosneva mängulaua (Rubic-u ruudu) ja teeb iga ruudukese @Nme tundlikuks vasak- ja parempoolse nupu klõpsudele; kuna mängulaua algasendisse viimine, selle segamine ja käik-käigu järel lahenduse näitamine ei ole seotud mingi konkreetse ruudukesega, vaid kogu mängulauaga, lisatakse neid genereerivate klõpsude tunnistajad tase kõrgemal, kogu liitobjektile.

make_board(N) :-
new(C1, click_gesture(left, '', single,
message(@prolog, switch_colours, @receiver,0))),
new(C2, click_gesture(right, '', single,
message(@prolog, switch_colours, @receiver,1))),
new(C3, click_gesture(left, '', double,
message(@prolog, algasend))),
new(C4, click_gesture(left, 's', double,
message(@prolog, sega))),
new(C5, click_gesture(left, 's', single,
message(@prolog, lahenda))),
forall((between(0, N, X), between(0, N, Y)),
(GX is X * 32,
GY is Y * 32,
concat_atom([X,'_',Y],Nme),
send(@rubic, display,
new(@Nme, box(32,32)),
point(GX, GY)),
send(@Nme, name, Nme),
send(@Nme, recogniser, C1),
send(@Nme, recogniser, C2),
send(@rubic, recogniser, C3),
send(@rubic, recogniser, C4),
send(@rubic, recogniser, C5))).

Predikaat algasend viib kogu mängualua algseisu; mängu teeb tunduvalt raskemaks ja huvitavamaks, kui ridades ja veergudes värvid vahelduvad, s.t. värvide määramisel kasutatakse praegu väljakommenteeritud rida:

algasend :-
dim(N),
forall(
(between(0,N,X),between(0,N,Y)),
(%Col_Ind is 2, %siis saame kõigil ruudukestel sama (punase)värvi
Col_Ind is (X+Y+1) mod N, %sama värv diagonaalidel - raskem!
colours(Col_Ind, Colour),
concat_atom([X,'_',Y],Nme),
send(@Nme,fill_pattern,colour(Colour)))).
colours(0,black).
colours(1,white).
colours(2,red).
colours(3,green).
colours(4,blue).
colours(5,yellow).
colours(6,magenta).
colours(7,cyan).
colours(8,violet).
colours(9,grey).
(Swi-Prolog kasutab värvidele nimede andmisel Unix-i aknasüsteemis X11 kasutatavaid nimesid, vt. värvitabelit)

Algasendisse viimise võiks kirjeldada ka veidi teisiti:

algasend1:-
send(@rubic?graphicals, for_all, message(@prolog, orig_colour, @arg1)).

orig_colour(Gr) :-
get(Gr,name,Nm),
atom_concat(Nm1,YY,Nm),atom_concat(XX,U,Nm1),U = '_',
atom_number(XX,X),
atom_number(YY,Y),
dim(N),
Col_Ind is (X+Y+1) mod N,
colours(Col_Ind, Colour),
send(Gr,fill_pattern,colour(Colour)).

Käigu (värvide vahetuse päripäeva või vastupäeva, sõltuvalt teisest argumendist) sooritab predikaat switch_colours; siin peab veidi nipitama, et ruudu (box) nimest kujul X_Y (eraldaja on vajalik, kui tahetakse kasutada ka 9x9-st suuremat mänguruutu) kätte saada (arvudena - nimi on aatom!) koordinaadid X,Y; reas- ja veerus teisenduse sooritamisel peab jälgima, et klikatud ruutu ei teisendataks kaks korda :

switch_colours(Gr,Suund) :-
get(Gr,name,Nm),
atom_concat(Nm1,YY,Nm),atom_concat(XX,U,Nm1),U = '_',
atom_number(XX,X),
dim(N),
forall(between(0, N, IY),
(concat_atom([X,'_',IY],INme),switch_colour(@INme,Suund))),
atom_number(YY,Y),
forall((between(0, N, IX),not(IX = X)),
(concat_atom([IX,'_',Y],INme),switch_colour(@INme,Suund))).

switch_colour(Gr,Suund) :-
get(Gr,fill_pattern,colour(Current)),
colours(Ind,Current),
(Suund=0,!,J is (Ind+1) mod 6; J is (Ind-1+6) mod 6),
colours(J,New),
send(Gr,fill_pattern,colour(New)).

Mängulaua segamisel salvestatakse tehtud käikude (X,Y,Suund) nimistu predikaadi segatud argumendina; lahenduse näitamisel võetakse sealt kõige esimene (s.t. viimasena sooritatud) käik ja tehakse vastupidine operatsioon; et mängija näeks, millist ruutu kasutatakse, muudetakse taimeri abil vastava ruudu piirjoon poole sekundi ajaks paksemaks; selle aja möödumisel käivitab taimer alarm predikaadi setpen, mis muudab kõigi ruudukeste piirjooned taas normaalseks:

sega:-
N is random(5) + 1, % 1..5 käiku
/*samme(N), %kui tahetakse ise määrata mitu sammu segamisel tehakse */
sega(N).

sega(K):-
dim(N),
sega(0,K,[],N).

sega(K,K,S,_):-!,
retractall(segatud(_)),assert(segatud(S)).

samme(3).

sega(K1,K,S,N):-
X is random(N),Y is random(N),
Suund is random(2), %s.t. 0 või 1
concat_atom([X,'_',Y],Nme),
%write(Nme),nl,
switch_colours(@Nme,Suund),
K2 is K1+1,
sega(K2,K,[[X,Y,Suund]|S],N).

lahenda:-
(segatud([[X,Y,Suund]|S]),!,
concat_atom([X,'_',Y],Nme),
Suund1 is 1-Suund,
send(@Nme,pen,5),
alarm(0.5,setpen,Id,[remove(true)]),
switch_colours(@Nme,Suund1),
retractall(segatud(_)),assert(segatud(S));true).

setpen:-
send(@rubic?graphicals, for_all,message(@arg1,pen, 1)).


Ülesandeid:
1. Ylesande tekst


Küsimused, probleemid: ©2004 Jaak Henno