Mõttemeister

Mõttemeister (Mastermind) on mälumäng, kus üks mängijatest (siin: inimene) mõtleb välja neljast erinevast värvist koosneva salajase koodi ja teine mängija (siin: arvuti) peab koodi ära arvama. Koodi arvamiseks pakub arvaja mingi koodi ja küsib koodi valdavalt mängijalt:
Mitu värvi on pakutud koodis täpselt õigel kohal ?
Mitu õiget värvi pakutud koodis üldse esineb (kuigi võibolla valel kohal).
Esimese küsimuse vastust (number 1..4) nimetatakse pakutud koodi sõnnide arvuks, teise küsimuse vastust - lehmade arvuks; mäng jätkub, kuni arvaja pakub välja õige koodi (s.t. sellise, kus sõnnide arv on 4).
Iga pakutud koodi vastustega (Sõnnid/Lehmad) saab arvaja lisainformatsiooni salajase koodi kohta. Arvaja pakutud koodile saadud vastused (Sõnnid/Lehmad) määravad piirkonna, kus otsitav kood asub: kui arvaja pakkus näiteks koodi [sinine, punane, valge, must] ja sai vastuseks Sõnnid=1, Lehmad=3, siis on järgmisena mõtet pakkuda vaid koode, mille võrdlemisel koodiga [sinine, punane, valge, must] saadakse vastuseks Sõnnid=1, Lehmad=3 (sest salajane kood asub selles piirkonnas). Uue pakkumise tegemisel peab arvesse võtma kõigi seni tehtud pakkumistele saadud vastuseid, s.t. pakkumine peab asuma kõigi seni tehtud pakkumiste Sõnnid/Lehmad määratud piirkondes.
Inimene (enamasti) ei suuda kogu saadud infot iga järgmise arvamise tegemisel arvesse võtta ja mäng võib kesta tunduvalt kauem, kuid Prologi jaoks on kogu senise info arvestamine ja optimaalse pakkumise tegemine lihtne.
Et neid arvesse võtta, salvestab Prolog kõik tehtud pakkumised nimistus Pakkumised, mille elementideks on kolmeelemendilised alamnimistud: pakkumine (neljast värvi nimest koosnev nimistu) ja sellele saadud vastused (Sõnnid/Lehmad). Predikaat lahenda otsib uue koodi Kood ja laseb siis predikaatidel sobib, arvestab kontrollida, kas see kood asub kõigi seniste pakkumiste vastustena saadud alampiirkondades. Kui predikaat arvestab leiab, et kood Kood ei asu mingi pakkumise poolt tehtud piirkonnas, toimub tagurdamine (backtracking) ja predikaat kood otsib uue koodi Kood; kui sobiv kood on leitud, küsib predikaat kysi mängijalt selle Sõnnid/Lehmad väärtused; kui Sõnnid=4, on mäng läbi, vastasel korral lisatakse viimasena tehtud pakkumine koos oma Sõnnid/Lehmad väärtustega nimistusse Pakkumised ja mäng jätkub:
lahenda(Pakkumised):-
kood(Kood),
sobivad(Kood,Pakkumised),
kysi(Kood,Sõnnid,Lehmad),
jatka([[Kood,Sõnnid,Lehmad]|Pakkumised]).
arvestab(Kood,[Kood1,Sõnnid,Lehmad]):-
yhiseid(Kood,Kood1,Lehmad1),
Lehmad = Lehmad1,
täpselt(Kood,Kood1,Sõnnid1),
Sõnnid = Sõnnid1,!.
sobivad(_,[]):-!.
sobivad(Kood,[Pakkumine|Pakkumised]):-
arvestab(Kood,Pakkumine),!,
sobivad(Kood,Pakkumised).
kysi(Kood,Sõnnid,Lehmad):-
write('Pakun: '),
write(Kood),
nl,
write('Mitu värvi on täpselt õigel kohal ? '),
read(Sõnnid),
write('Mitu õiget värvi esineb ? '),
read(Lehmad).
jatka([[_,4,_]|Pakkumised]):- !,
length(Pakkumised,N),
write('Tegin '),
write(N),
write(' vigast pakkumist '),
nl,
lopeta.

jatka(Pakkumised):-
lahenda(Pakkumised).

Mängu alustab ja lõpetab kasutajaliidese predikaat mäng:
mäng:-
nl,
write(' Ma olen Koodarvamise meister! '),
nl,
write('Mõtle välja nelja värvi kood (kõik värvid erinevad).'),
nl,
write('Kasutada võib järgnevaid värve: '),
nl,
saab_kasutada(Värv),
write(Värv),
write(' '),
fail.

mäng:-
nl,
write('Kas oled valmis? (j/e)'),nl,
read(V),
((V = 'j' , ! , lahenda([])) ; lopeta).

lopeta :-
write('Nägemist! Kui tahad veel kord proovida, kirjuta: mäng.'),
nl.

Abipredikaadid yhiseid ja täpselt leiavad kahe koodi ühiste elementide arvu (Lehmad) ja elementide arvu, mis mõlemates koodides on samasugused (Sõnnid):
yhiseid([Värv|Värvid],Kood,N):-
member(Värv,Kood),!,
yhiseid(Värvid,Kood,N1),
N is N1 + 1,!.

yhiseid([Värv|Värvid],Kood,N):-
not(member(Värv,Kood)),
yhiseid(Värvid,Kood,N),!.
yhiseid([],_,0).

täpselt([Värv|Värvid],[Värv|Värvid1],N):-
täpselt(Värvid,Värvid1,N1),!,
N is N1 + 1.

täpselt([Värv|Värvid],[Värv1|Värvid1],N):-
not(Värv = Värv1),!,
täpselt(Värvid,Värvid1,N),!.
täpselt([],[],0)


Ülesandeid:
1. Labürindis taga-ajamise mäng kirjeldab, kuidas Theseus (T) püüdis labürindis Minotaurust (M). Mängu algul on Theseus mingis labürindi ruumis ja Minotaurus - teises ruumis. Mängijad siirduvad kordamööda ruumist teise (samasse ruumi ei või käigul olev mängija jääda). Theseus peab Minotauruse kinni püüdma (mõlemad mängijad on samas ruumis), aga Minotaurus peab siirduma ruumi, kus Theseus oli mängu algul; kui ta suudab seda, siis on Minotaurus võitnud. Näiteks kõrvalolevatel joonistel esimeses võidab alati Theseus (ükskõik kumma käigul olles), kuid teises võidab Theseus (kindlasti) vaid siis, kui Minotaurus on käigul.
Õpeta Prolog mängima Theseusena; võitmiseks peab Theseus kogu aeg kontrollima ruumide vahelist kaugust (iga käigu ajal püüdma lühendada enda ja Minotauruse vahemaad) ja kaugusi omast ja Minotauruse ruumist enda stardiruumini - Minotaurus ei tohi jõuda stardiruumile lähemale kui ta ise on.
Labürindi kirjeldus peaks olema eraldi predikaadis ja failis ja laaditakse programmikäsuga consult('labyrint.pro'); testimiseks võib kasutada näiteks kõrvalolevat labürinti - kui Theseus siin ei suuda Minotaurust kuue esimese käiguga kätte saada, võidab Minotaurus. lähemale kui ta ise on - muidu Minotaurus jõuaks lõpetada.



Küsimused, probleemid: ©2004 Jaak Henno