Feladat Készítsünk alkalmazást, amely bemutatja a misszionárius-kannibál problémát! Adott egy folyó, amelynek az egyik partján n darab kannibál és n darab misszionárius várakozik, hogy átkeljenek. Átkelésükhöz adott továbbá egy csónak, amely maximum k személyt tud egyszerre szállítani. Az átkelések alkalmával nem szabad, hogy egy időben akár a csónakban, akár valamelyik parton több kannibál legyen, mint misszionárius, kivéve, ha ott egyetlen misszionárius sem tartózkodik. Az elkészített program letölthető honlapomról: http://people.inf.elte.hu/juadaml/bin/bead/eaf3hf1s.exe, vagy http://people.inf.elte.hu/juadaml/bin/bead/eaf3hf1s.zip. Felület megtervezése A felületen 3 lista doboz jelenik meg, 2 a két partnak, 1 a csónaknak, és köztük 2 gomb, amely felváltva a csónakba behelyezi a kiválasztott személyeket, valamint átúsztatja a csónakot az egyik partról a másikra. A felületen jelenik meg még a személyek számát, valamint a csónak férőhelyének meghatározó beállítások is, amelyet letiltok a játék menete alatt. Ha nem megy játék, akkor engedélyezem, és a játékvezérlő elemeit tiltom le. Lehetőséget adok a játék feladására, amely azért is érdekes, mert lehetőség van lehetetlen felállás előállítására. Az eredmény táblát LISTVIEW vezérlővel valósítom meg, így lehet több oszlopot felvenni, valamint csoportosítani az elemeket. Mind e mellett fel kell vennem egy nevet bekérő ablakot. 1
Megvalósítás Osztály diagram Form frmmain frminput frmscores Coast: bool ingame: bool Score: scorelist AddScores( ): void StartGame():void EndGame(): void LoadScore():void SaveScore(): void Állapot diagram Eredménytábla Név megadása ingame ingame Játék beállítása Játék Fontosabb függvények, eseménykezelők és típusok Pontszám, eredmények Hogy egyszerűen tudjuk menteni, valamint betölteni az eredményeket, származtatunk egy List<ScoreRec> osztályból egy Serializable scorelist osztályt, ahol a ScoreRec egy Serializable struktúra, mely a nevet lépésszámot, személyek számát és csónak létszámát tárolja. A scorelist osztályban nem kell új függvényeket létrehoznunk, hisz csak azért példányosítottuk, hogy Serializable lehessen. 2
Így az eredmények betöltése, és mentése azzá az egyszerű feladattá válik, hogy vizsgálnunk kell, hogy létezik a SCORES.SAV fájl, és betölteni, valamint menteni kell azt. private bool LoadScores() if (!File.Exists("scores.sav")) return false; BinaryFormatter bifromer = new BinaryFormatter(); Stream istream = File.Open("scores.sav", FileMode.Open, FileAccess.Read); Score = (scorelist)bifromer.deserialize(istream); istream.close(); return true; private void SaveScores() BinaryFormatter biformer = new BinaryFormatter(); if (!File.Exists("scores.sav")) File.Create("scores.sav").Close(); Stream ostream = File.Open("scores.sav", FileMode.Truncate, FileAccess.Write); biformer.serialize(ostream, Score); ostream.close(); Előzmények frissítése A History_Add(ListBox, bool) függvényhívás a megtett lépéseket, azaz átúszásokat írja le, és ezek alapján rendezi a dicsőségtáblán a játékosokat. Azt kell megadni paraméterének, hogy mely irányba haladt, és hogy kik. (Ez a csónak lista.) private void History_Add(ListBox lst, bool cst) int mis=countlistitems(lst.items, "Misszionárius"), can=countlistitems(lst.items, "Kannibál"); string side; if (Coast) side = "-->"; else side = "<--"; lststeps.items.add(convert.tostring(mis) + " M & " + Convert.ToString(can) + " K " + side); lststeps.selectedindex = lststeps.items.count - 1; Játék indítása/feladása gomb Ha megy a játék, akkor leállítom az EndGame(EndType.Cancel) függvényhívással, egyébként elindítom a StartGame() függvényhívással. Mind a két függvény módosítja a vezérlők állapotát, valamint az indító gomb szövegét, de a StartGame() a listák ürítése után a misszionáriusokat, és a kannibálokat is létrehozza, az EndGame(EndType) függvény meg EndType.Won hívás esetén felveszi a játékost a dicsőség táblára. Az EndType enumeráció az EndGame() függvény számára van létre hozva, és két értéke van, Won és Cancel. 3
private void StartGame() /// Controlls ///... ingame = true; Coast=false; /// Game /// lststeps.items.clear(); lstcoastleft.items.clear(); lstcoastright.items.clear(); lstboat.items.clear(); for (int i = 0; i < nudpeople.value; ++i) lstcoastleft.items.add("misszionárius"); /// Nehogy már felváltva legyen misszionárius és kannibál! /// Elég az, hogy a felhasználó úgyis össze kavarja! for (int i = 0; i < nudpeople.value; ++i) lstcoastleft.items.add("kannibál"); private void EndGame(EndType status) /// Controlls ///... ingame = false; /// Game /// if (status == EndType.Won) frminputbox InputForm = new frminputbox(); frmscore ScoreTable = new frmscore(); if (InputForm.ShowDialog(this) == DialogResult.OK) name = InputForm.txtName.Text; AddScores(name, lststeps.items.count, nudpeople.value, nudcapacity.value); InputForm.Close(); ScoreTable.ShowDialog(this); Átúsztatás/cserélés gombok A listadobozok közötti gombok, hol a csónak átúsztatására szolgálnak, hol a személyek cserélgetésére szolgálnak, de mind a kettőnek hasonló a szerkezete: Attól függően, hogy hol van kikötve a csónak, úsztat, vagy cserél. A cserélés abból áll, hogy megvizsgálja, hogy a csónakba belefér a megfelelő számú utas, majd a csónakból kiszedi a kiválasztott személyeket egy ideiglenes változóba, bele teszi a csónakba a parton kiválasztott személyeket, és az ideiglenes változóból kiteszi a partra a személyeket. A kiválasztottak kiszedését úgy végzem el, hogy végig iterálok a kiválasztottak listáján, átmásolom a céllistába, közben bele teszem egy ideiglenes változóba, és ennek segítségével kiveszem a kiválasztottakat az eredeti listából. 4
Az úsztatás abból áll, hogy megvizsgálja, hogy a szabálynak megfelelő személyeket akarjuk átúsztatni, majd ha megfelelt, akkor át állítja a túlpartra a csónakot, és módosítja a két gomb szövegét. private void btncoastleft_click(object sender, EventArgs e) if (!Coast) // Exchange int ccount = 0; ccount += lstboat.items.count - lstboat.selecteditems.count; ccount += lstcoastleft.selecteditems.count; if (ccount > nudcapacity.value) MessageBox.Show("Nem férünk bele a csónakba!", "Átszállás", else... else // Transfer if (lstboat.items.count == 0) MessageBox.Show("A csónakban senki se evezik.", "Átúszás", else if (countlistitems(lstcoastright.items, "Misszionárius") < countlistitems(lstcoastright.items, "Kannibál") && (countlistitems(lstcoastright.items, "Misszionárius")!= 0)) MessageBox.Show("Ne hagyjatok minket itt!", "A misszionáriusok sírnak", else if ((countlistitems(lstboat.items, "Misszionárius") + countlistitems(lstcoastleft.items, "Misszionárius")) < (countlistitems(lstboat.items, "Kannibál") + countlistitems(lstcoastleft.items, "Kannibál")) && (countlistitems(lstcoastleft.items, "Misszionárius") + countlistitems(lstboat.items, "Misszionárius")!= 0)) MessageBox.Show("Nem kell sietned!", "A misszionáriusok sírnak", else Coast = false; btncoastleft.text = varchange; btncoastright.text = vartransfer; History_Add(lstBoat, Coast); if (lstcoastleft.items.count == 0) EndGame(EndType.Won); 5
Fejlesztési lehetőségek Lista dobozok helyett vizuális megjelenítés, azaz az emberek körökkel vannak reprezentálva, a folyón egy felülnézeti csónakban úsznak át a túlpartra, valamint a gombok a rendelkezésre álló hely közepén jelennek meg, és esetleges háttérzenével színesítve. Több kategória felvétele az eredmény táblába: egyszerű (2x2, 2), klasszikus (3x2, 2), középnehéz (4x2, 3), nehéz (5x2, 3), könnyű (nx2, 4), *lehetetlen (nx2, k)+. Nagyobb, reálisabb, vagy szórakoztatóbb eredmény lista létrehozása az alap kategóriákban, ezzel meghatározva a kategóriák sorrendjét, az előző pontban meghatározott sorrendben. Mindegyik kategóriába legfeljebb három legyőzhető személy szerepel, a legkisebb lépésszámtól távol. A kategóriák megválasztásának lehetősége. Ablak méretezhetőség lehetőségének megadása. Az eredményeket az APPDATA könyvtárban való tárolása. A fejlesztési lehetőségek megvalósításához le lehet tölteni honlapomról a forráskódját: http://people.inf.elte.hu/juadaml/bin/bead/eaf3hf1.zip. 6