V tomto tutoriáli si spolu naprogramujeme kartovú hru Čierny Peter. Použijeme programovací jazyk Java a zameriame sa na to, aby sme použili OOP prístup, teda objektovo-orientované programovanie.
Pravidlá hry
Najskôr si musíme zanalyzovať danú hru. To spravíme tak, že si povieme a určíme pravidlá. V hre je 33 kariet. Jedna karta nemá pár, ostatné ho majú. Hru môže hrať 3 až 6 hráčov. Všetky karty sa rozdajú medzi hráčov. Automaticky si hráči vytriedia z ruky karty, ktoré majú páry. S ostatnými sa začína hra. Ten, čo má najviac kariet, nechá ťahať hráča po svojej pravici. Ak ten hráč získal pár, tak ho vyloží a ďalší hráč od neho ťahá kartu. Ak hráč prišiel o všetky karty, už viac nehrá. Komu ostane posledná karta, ten prehral hru.
Analýza hry – vytváranie objektov
Teraz je čas pripraviť si popis našich tried, rozhraní a podobne. V skratke, uvažujeme nad pravidlami, okolnosťami a členmi danej hry a chceme ich pretvoriť na objekty.
Čím všeobecnejšie napíšeme naše objekty, tým lepšie pre ich znovupoužiteľnosť. Ak by sme chceli niekedy naprogramovať karty žolíkové, sedmové alebo hocijaké iné, tak si nechajme túto možnosť. Teda napríklad vytváranie inštancií kariet nedávajme do triedy balíku, ale inde.
Postup:
vytvorím kartu
vytvorím balík kariet
vytvorím hráča
interakciu s používateľom
správu hry
logiku hry Čierny Peter
Karta
Každá hracia všeobecná karta má nejaké špecifikum. Je to kráľ srdcový, kráľ pikový a podobne. V našom prípade máme páry a každá karta v páre je iná, spoločné majú to, že sú pár. Ako napríklad v žolíkových kartách máme 4 kráľov. Každý je iný, ale majú spoločné, že sú to králi.
package sk.jaro.CiernyPeter;
public class Karta {
private int cisloKarty; //každá karta ma iné číslo
private int cisloParu; //každý prá má iné číslo, len dve karty majú to
isté číslo páru
public Karta(int cisloKarty, int cisloParu) {
this.cisloKarty = cisloKarty;
this.cisloParu = cisloParu;
}
public int getCisloKarty() {
return cisloKarty;
}
public int getCisloParu() {
return cisloParu;
}
}
Balík kariet
Ďalej budeme potrebovať tieto karty uložiť do balíku. Každá hra má niekoľko kariet, ktoré tvoria balík. Takže náš balík bude obsahovať zoznam kariet. Čo sa dá robiť s balíkom? Napríklad miešať karty, alebo z balíku môžeme vybrať kartu. Keď vyberám karty alebo miešam karty, tak tam musia nejaké byť. Lebo ak vyberiem postupne všetky karty z balíku, tak nakoniec budem mať balík prázdny. Skúste miešať prázdny balík kariet :) Preto si vytvorím aj pomocnú metódu, ktorá zistí, či je balík prázdny alebo nie.
package sk.jaro.CiernyPeter;
import java.util.Collections;
import java.util.List;
public class BalikKariet {
private List<Karta> karty; //implementacia listu pre zachovanie poradia
public BalikKariet(List<Karta> karty) {
this.karty = karty;
}
public List<Karta> getKarty() {
return karty;
}
public void zamiesajKarty(){
if(!jeBalikPrazdny())
Collections.shuffle(karty);
}
private boolean jeBalikPrazdny(){
return karty == null || karty.isEmpty();
}
public Karta getKartu(){
Karta karta = null;
if(!jeBalikPrazdny()) {
karta = karty.get(0); //vytiahnem prvú kartu
karty.remove(karta); //kartu odstránim z balíku
}
return karta;
}
}
Hráč
Do každej hry potrebujem hráča, teda niekoho kto bude danú hru hrať. Rozhodol som sa, že hráčovi dám meno a karty v ruke. Keď vytváram nového hráča pomocou new, tak sa zavolá konštruktor danej triedy a tam si všimni, že som mu do ruky nedal nič, teda tam má prázdno. To preto, lebo ešte nedostal nijakú kartu pri rozdávaní, ale musí mať nejaké úložisko kde mu ich dám :)
Je tam ešte metóda, ktorá má na starosti odstrániť z ruky hráča všetky páry. Kto by si to chcel nejako zovšeobecniť, tak môže. Teda do objektu Hrac, by dal len metódu na odstránenie jednej karty, alebo zoznamu kariet. A ktoré karty to budú to nechá na iný objekt, ktorý spravuje pravidlá hry Čierny Peter.
package sk.jaro.CiernyPeter;
import java.util.ArrayList;
import java.util.List;
public class Hrac {
private String meno;
private List<Karta> kartyVRuke;
public Hrac(String meno) {
this.meno = meno;
this.kartyVRuke = new ArrayList<>();
}
public String getMeno() {
return meno;
}
public List<Karta> getKartyVRuke() {
return kartyVRuke;
}
public void odstranParyZRuky() {
ArrayList<Karta> akeKartyOdstraniZRuky = new ArrayList<>();
for(Karta karta : kartyVRuke){
try {
for (Karta k : kartyVRuke) {
if (karta.getCisloParu()
== k.getCisloParu()
&& karta.getCisloKarty() != k.getCisloKarty()) {
akeKartyOdstraniZRuky.add(karta);
akeKartyOdstraniZRuky.add(k);
break;
}
}
}catch (Exception e){
e.printStackTrace();
}
}
kartyVRuke.removeAll(akeKartyOdstraniZRuky);
}
}
Ovládanie hry
Akú chceš spraviť aplikáciu? Ako chceš komunikovať s používateľom? Cez grafické rozhranie? Cez konzolu, alebo inak? Teraz budeme robiť konzolovú interakciu, ale ak by si chcel robiť v budúcnosti grafické rozhranie, tak je vynikajúca idea spraviť interface, teda rozhranie, kde popíšem metódy aké chcem používať na interakciu s používateľom. Potom keď budeš robiť grafické rozhranie, tak si len zaimplementuješ toto nové rozhranie a niekde v kóde hry povieš, že teraz používať túto implemntáciu, a nemusíš prepisovať aj celú hru, lebo metódy sú tam rovnaké, len z iného zdroja.
Čo potrebujeme vypísať používateľovi, alebo čo od neho chcem získať? Počet hráčov, ich mená, akú kartu chceme hráčovi zobrať a chceme vypísať koniec hry. Ak chceš niečo viac, tak si to kľudne dorob.
package sk.jaro.CiernyPeter.rozhrania;
import sk.jaro.CiernyPeter.Hra;
import sk.jaro.CiernyPeter.Hrac;
public interface IOvladanieHry {
int vyberPocetHracov();
Hrac getMenoHraca(int i);
int zoberKartu(Hrac hrac1, Hrac hrac2);
void vypisKtoPrehral(Hra hra);
}
Teraz si musíme zaimplementovať toto rozhranie. Teraz máme len predpis metód ale nie ich vnútro. Budeme používať konzolu, ktorú budeme čítať pomocou scanneru a na konci hry si uzavrieme scanner. Každá metóda je jednoduchá, vypíšem na konzolu čo chcem a potom nechám používateľa, aby mi to napísal.
Všimni si, keď berieš nextInt(), tak sa to pokúsi zobrať číslo. Ak nájde niečo iné je to chyba a tú ošetríme. Kľudne si dorob viac ošetrení, podmienok a výpisov. Potom ale musíš zobrať aj zvyšok. Lebo čo spravil používateľ? Zadal číslo a stlačil enter. Ty si zobral len to číslo, ale nie aj enter. Preto tam máme ešte nextLine – to nám zoberie zvyšok riadku aj s enterom.
Čierny Peter bude hráč, ktorý ostal posledný v hre.
package sk.jaro.CiernyPeter.gui;
import sk.jaro.CiernyPeter.Hra;
import sk.jaro.CiernyPeter.Hrac;
import sk.jaro.CiernyPeter.rozhrania.IOvladanieHry;
import java.util.Scanner;
public class OvladanieHry implements IOvladanieHry {
Scanner scanner = new Scanner(System.in);
@Override
public int vyberPocetHracov() {
int pocetHracov = 0;
System.out.println("Zadaj počet hráčov (3 až 6):");
try {
pocetHracov = scanner.nextInt();
scanner.nextLine();
} catch (Exception ex) {
System.out.println("Nepodarilo sa načítať počet hráčov. Zadal si správne číslo?");
pocetHracov = vyberPocetHracov();
}
return pocetHracov;
}
@Override
public Hrac getMenoHraca(int i) {
Hrac hrac = null;
System.out.println(String.format("Zadaj meno pre hráča %d :", i));
String meno = scanner.next();
scanner.nextLine();
if (meno.equals("") || meno.equals(" ")) {
System.out.println(String.format("Prosím znovu zadajte meno pre hráča %d :", i));
hrac = getMenoHraca(i);
} else {
hrac = new Hrac(meno);
}
return hrac;
}
@Override
public int zoberKartu(Hrac hrac1, Hrac hrac2) {
int zoberKartuCislo = 0;
System.out.print(hrac1.getMeno() + " ,ktorú kartu cheš zobrať hračovi "+hrac2.getMeno()+"?: ");
for(int i = 0; i < hrac2.getKartyVRuke().size(); i++){
System.out.print(i+", ");
}
try {
zoberKartuCislo = scanner.nextInt();
scanner.nextLine();
} catch (Exception ex) {
System.out.println("Nepodarilo sa získať akú kartu chceš zobrať. Zadal si správne číslo?");
zoberKartuCislo = zoberKartu(hrac1,hrac2);
}
return zoberKartuCislo;
}
@Override
public void vypisKtoPrehral(Hra hra) {
System.out.println("Čierny Peter je hráč "+hra.getHraci().get(0).getMeno());
scanner.close();
}
}
Hra
Každá hra má niekoľko hráčov, má balík kariet s ktorými sa hrá a má aj ovládanie. Toto si zadefinujeme.
public class Hra{
private BalikKariet balikKariet;
private int pocetHracov;
private List<Hrac> hraci;
private OvladanieHry ovladanieHry;
V konštruktore tejto Hry si nastavíme to, čo vieme:
public Hra() {
this.ovladanieHry = new OvladanieHry();
this.pocetHracov = ovladanieHry.vyberPocetHracov();
this.hraci = vytvorHracov();
}
Nestavili sme balík kariet, pretože, ten je špecifický pre každý typ hry iný. V našom prípade sú to karty pre hru Čierny Peter. Tak tie si vytvorím neskôr.
V kuse kódu vyššie sme si vytvorili inštanciu ovládania hry a hneď sme ju aj použili pri výbere počtu hráčov. Metóda výber hráčov je jednoduchá, používateľa aplikácie sa pýtam ako sa volajú a rovno ich vytvorím a dám do zoznamu.
public List<Hrac> vytvorHracov() {
ArrayList<Hrac> hraci = new ArrayList<>();
for(int i = 0; i < pocetHracov; i++){
Hrac hrac = ovladanieHry.getMenoHraca(i+1);
hraci.add(hrac);
}
return hraci;
}
Logiku hry spustím a teda začnem ju hrať keď zavolám metódu zacniHrat.
public void zacniHru() {
HraCiernyPeter ciernyPeter = new HraCiernyPeter();
//vseobecna logika ku kazdej hre
balikKariet = vytvorBalik(ciernyPeter.vytvorKarty());
balikKariet.zamiesajKarty();
//rozdaj karty z baliku
ciernyPeter.rozdajKarty(this);
// pre hru urcim prveho hraca
// v ciernom petrovi je to hrac s najviac kartami a ten zacina tahat
Hrac prvyHrac = ciernyPeter.getHracaSNajviacKartami(getHraci());
//vsobecne na zaklade prveho hraca zistim jeho poradie v zozname hracov v hre
int prvyHracIndex = getHraci().indexOf(prvyHrac);
ciernyPeter.zlozHracomParyZRuky(this);
ciernyPeter.odstranHracovZHry(this);
if(!ciernyPeter.jeKoniecHry(this)){
//idu do kruhu az kym hraju aspon dvaja hraci
ciernyPeter.kolobehHry(this,prvyHracIndex);
}
}
Tu si vytvorím inštanciu triedy HraCiernyPeter, ktorá má na starosti logiku, ktorá je špecifická práve pre tento typ hry. Tú si vytvoríme neskôr.
Na tomto mieste si vytvorím aj balík kariet pomocou kariet, ktoré sa vytvárajú v triede HraCiernyPeter. Keďže som zvolil názvy metód také, aby sa ľahko chápali, tak tušíme čo dané metódy robia. Keď vytvorím balík a idem hrať, tak karty pomiešam, potom ich rozdám hráčom.
Musím si určiť, ktorý hráč začína ako prvý. V čiernom petrovi je to ten, čo má najviac kariet.
Ako sme si povedali na začiatku, tak keď majú hráči rozdané karty, tak si zložia všetky páry a tým sa zbavia nejakých kariet. Skontrolujem či azda niekto nemal všetko páry na ruke a tým pádom skončil v hre. Spýtam sa, či je koniec hry – či ostal len jeden hráč, ktorý má čierneho petra – lebo táto karta nemá pár. Ak nie, tak začnem kolobeh hry.
V tejto triede mám aj iné pomocné triedy. Skús si ich prejsť sám.
package sk.jaro.CiernyPeter;
import sk.jaro.CiernyPeter.gui.OvladanieHry;
import java.util.ArrayList;
import java.util.List;
public class Hra{
private BalikKariet balikKariet;
private int pocetHracov;
private List<Hrac> hraci;
private OvladanieHry ovladanieHry;
public Hra() {
this.ovladanieHry = new OvladanieHry();
this.pocetHracov = ovladanieHry.vyberPocetHracov();
this.hraci = vytvorHracov();
}
public BalikKariet getBalikKariet() {
return balikKariet;
}
public List<Hrac> getHraci() {
return hraci;
}
public OvladanieHry getOvladanieHry() {
return ovladanieHry;
}
public List<Hrac> vytvorHracov() {
ArrayList<Hrac> hraci = new ArrayList<>();
for(int i = 0; i < pocetHracov; i++){
Hrac hrac = ovladanieHry.getMenoHraca(i+1);
hraci.add(hrac);
}
return hraci;
}
public BalikKariet vytvorBalik(List<Karta> karty) {
return new BalikKariet(karty);
}
public void odstranHracaZHry(Hrac hrac) {
//ak ma prazdnu ruku odstranim ho
if(hrac.getKartyVRuke().isEmpty()){
getHraci().remove(hrac);
}
}
public void ukonciHru() {
ovladanieHry.vypisKtoPrehral(this);
}
public void zacniHru() {
HraCiernyPeter ciernyPeter = new HraCiernyPeter();
//vseobecna logika ku kazdej hre
balikKariet = vytvorBalik(ciernyPeter.vytvorKarty());
balikKariet.zamiesajKarty();
//rozdaj karty z baliku
ciernyPeter.rozdajKarty(this);
// pre hru urcim prveho hraca
// v ciernom petrovi je to hrac s najviac kartami a ten zacina tahat
Hrac prvyHrac = ciernyPeter.getHracaSNajviacKartami(getHraci());
//vsobecne na zaklade prveho hraca zistim jeho poradie v zozname hracov v hre
int prvyHracIndex = getHraci().indexOf(prvyHrac);
ciernyPeter.zlozHracomParyZRuky(this);
ciernyPeter.odstranHracovZHry(this);
if(!ciernyPeter.jeKoniecHry(this)){
//idu do kruhu az kym hraju aspon dvaja hraci
ciernyPeter.kolobehHry(this,prvyHracIndex);
}
}
}
Logika hry Čierny Peter
V tejto časti si vytvoríme karty špecifické pre túto hru. Teda 16 párov a jedného čierneho petra.
public List<Karta> vytvorKarty() {
ArrayList<Karta> karty = new ArrayList<>();
int j = 1;
for(int i = 0; i < 16; i++, j=j+2){
karty.add(new Karta(j, i));
karty.add(new Karta(j+1, i));
}
karty.add(new Karta(33,-1)); //Čierny Peter
return karty;
}
Keď rozdávam karty, tak ich rozdávam po jednej. Táto metóda by mohla byť aj v triede Hra, ale teoreticky pre iné typy hier by sa karty rozdávali inak. Tu rozdávam všetky karty.
Z balíku kariet zoberiem prvú kartu, z balíka ju odstránim a dám ju hráčovi do ruky. Tu je taký fígeľ, že idem cez všetky karty a robím modulo nad poradím karty s počtom hráčov, to mi zaručí, že budem dookola prechádzať hráčov až kým neskončí balík.
public void rozdajKarty(Hra hra) {
BalikKariet balikKariet = hra.getBalikKariet();
List<Hrac> hraci = hra.getHraci();
int pocetKariet = balikKariet.getKarty().size();
for(int i = 0; i<pocetKariet;i++){
Hrac hrac = hraci.get(i%hraci.size());
hrac.getKartyVRuke().add(balikKariet.getKartu());
}
}
Keď sa chystám odstrániť hráčov z hry (keď nemajú už žiadne karty na ruke), tak ich nemôžem odstrániť počas toho ako cez nich prechádzam (iterujem). Preto si ich dávam do pomocného zoznamu a až po iterácii ich odstránim.
public void odstranHracovZHry(Hra hra) {
//nemôžem mazať hraca z kolekcie ak cez nu prechadzam, preto si vytvorim novy zoznam a odstranim potom
ArrayList<Hrac> hraciNaOdstranenie = new ArrayList<>();
for(Hrac hrac : hra.getHraci()){
//skontrolujem ci uz niekto neskoncil, teda ma prazdnu ruku
//ak ano odstranim ho z hry
if(hrac.getKartyVRuke().isEmpty()){
hraciNaOdstranenie.add(hrac);
}
}
for(Hrac hrac : hraciNaOdstranenie){
hra.odstranHracaZHry(hrac);
}
}
Keď niekomu zoberiem kartu z ruky, tak každému z tých hráčov pomiešam karty. Jednému hráčovi zoberiem kartu z kolekcie kartičiek čo má na ruke a druhému pridám do kolekcie ďalšiu kartu.
public void zoberHracoviKartu(Hrac hrac1, Hrac hrac2, Hra hra) {
int poradieZobranejKarty = hra.getOvladanieHry().zoberKartu(hrac1,hrac2);
Karta vzataKarta = hrac2.getKartyVRuke().get(poradieZobranejKarty);
hrac1.getKartyVRuke().add(vzataKarta);
hrac2.getKartyVRuke().remove(vzataKarta);
//pomiesam karty v ruke
Collections.shuffle(hrac1.getKartyVRuke());
Collections.shuffle(hrac2.getKartyVRuke());
}
Samozrejme kolobeh hry ide nasledovne. Hráme dovtedy, kým nám v hre ostanú aspoň dvaja hráči. Začínam u prvého hráča, ktorý zoberie kartu druhému hráčovi. A tu som si natrafil na chybu. Predsa hráč s najväčším počtom kariet neťahá ale malo by sa ťahať jemu teda, ten čo je za ním ťahá od neho. Tak tu si to môžete opraviť, to nechám na vás. Pomôcka: upravte index prvého hráča v triede Hra, ak si pamätáte, tam sme ho určili.
public void kolobehHry(Hra hra, int prvyHracIndex) {
while (hra.getHraci().size() > 1) {
int pocetHracov = hra.getHraci().size();
Hrac hrac1 = hra.getHraci().get(prvyHracIndex%pocetHracov);
Hrac hrac2 = hra.getHraci().get((prvyHracIndex + 1)%pocetHracov);
zoberHracoviKartu(hrac1, hrac2,hra);
zlozHracomParyZRuky(hra);
odstranHracovZHry(hra);
if(jeKoniecHry(hra)) {
break;
}
prvyHracIndex++;
}
}
Tu je potom celý kód triedy aj s inými pomocnými metódami.
package sk.jaro.CiernyPeter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class HraCiernyPeter {
public List<Karta> vytvorKarty() {
ArrayList<Karta> karty = new ArrayList<>();
int j = 1;
for(int i = 0; i < 16; i++, j=j+2){
karty.add(new Karta(j, i));
karty.add(new Karta(j+1, i));
}
karty.add(new Karta(33,-1)); //Čierny Peter
return karty;
}
public void rozdajKarty(Hra hra) {
BalikKariet balikKariet = hra.getBalikKariet();
List<Hrac> hraci = hra.getHraci();
int pocetKariet = balikKariet.getKarty().size();
for(int i = 0; i<pocetKariet;i++){
Hrac hrac = hraci.get(i%hraci.size());
hrac.getKartyVRuke().add(balikKariet.getKartu());
}
}
public Hrac getHracaSNajviacKartami(List<Hrac> hraci) {
int max = 0;
Hrac hracMax = null;
for(Hrac hrac : hraci){
int size = hrac.getKartyVRuke().size();
if(max < size){
max = size;
hracMax = hrac;
}
}
return hracMax;
}
public void zlozHracomParyZRuky(Hra hra) {
for(Hrac hrac : hra.getHraci()) {
hrac.odstranParyZRuky();
}
}
public void odstranHracovZHry(Hra hra) {
//nemôžem mazať hraca z kolekcie ak cez nu prechadzam, preto si vytvorim novy zoznam a odstranim potom
ArrayList<Hrac> hraciNaOdstranenie = new ArrayList<>();
for(Hrac hrac : hra.getHraci()){
//skontrolujem ci uz niekto neskoncil, teda ma prazdnu ruku
//ak ano odstranim ho z hry
if(hrac.getKartyVRuke().isEmpty()){
hraciNaOdstranenie.add(hrac);
}
}
for(Hrac hrac : hraciNaOdstranenie){
hra.odstranHracaZHry(hrac);
}
}
public boolean jeKoniecHry(Hra hra) {
if(hra.getHraci().size() < 2){
hra.ukonciHru();
return true;
}
return false;
}
public void zoberHracoviKartu(Hrac hrac1, Hrac hrac2, Hra hra) {
int poradieZobranejKarty = hra.getOvladanieHry().zoberKartu(hrac1,hrac2);
Karta vzataKarta = hrac2.getKartyVRuke().get(poradieZobranejKarty);
hrac1.getKartyVRuke().add(vzataKarta);
hrac2.getKartyVRuke().remove(vzataKarta);
//pomiesam karty v ruke
Collections.shuffle(hrac1.getKartyVRuke());
Collections.shuffle(hrac2.getKartyVRuke());
}
public void kolobehHry(Hra hra, int prvyHracIndex) {
while (hra.getHraci().size() > 1) {
int pocetHracov = hra.getHraci().size();
Hrac hrac1 = hra.getHraci().get(prvyHracIndex%pocetHracov);
Hrac hrac2 = hra.getHraci().get((prvyHracIndex + 1)%pocetHracov);
zoberHracoviKartu(hrac1, hrac2,hra);
zlozHracomParyZRuky(hra);
odstranHracovZHry(hra);
if(jeKoniecHry(hra)) {
break;
}
prvyHracIndex++;
}
}
}
Main
Nakoniec som si vytvoril triedu s jednou main metódou, ktorá sa nám bude volať pri spustení programu.
public static void main(String[] args) {
Hra hra = new Hra();
hra.zacniHru();
}
Dorobte výpis, aké karty boli hráčovi odstránené z ruky, keď zložil páry. Spravte ďalšie podmienky pri zadávaní údajov od používateľa, aby nebral karty, ktoré niekto nemá v ruke a podobne.
🥇 Sme jednotka v online vzdelávaní na Slovensku. Na našom webe nájdeš viac ako 300 rôznych videokurzov z oblastí ako programovanie, tvorba hier, testovanie softwaru, grafika, UX dizajn, online marketing, MS Office a pod. Vyber si kurz, ktorý ťa posunie vpred ⏩