Diskuze: hra pacman
V předchozím kvízu, Online test znalostí Java, jsme si ověřili nabyté zkušenosti z kurzu.

Tvůrce

Zobrazeno 33 zpráv z 33.
//= Settings::TRACKING_CODE_B ?> //= Settings::TRACKING_CODE ?>
V předchozím kvízu, Online test znalostí Java, jsme si ověřili nabyté zkušenosti z kurzu.
dědičnost znamená, že má potomek stejné vlastnosti, jako předek (všechny vlastnosti předka). To ale neznamená, že musí být mezi nimi vazba 1:1. Potomek si může metody překrývat. Takže pokud má třeba pacman metodu jdi vpravo, tak potomek může mít podmínku v této metodě, že pokud nějaký objekt je v takovém a takovém stavu, tak pro něj to znamená, jdi vlevo.
V pacmanovi krásně využiješ nejrůznější návrhové vzory. Singleton,
state (o kterém jsem teď mluvil a ten se pro tu hru krásně hodí), to samé
třeba messenger, abys nebyl závislý na jednom konkrétním objektu, atd.
Jinak k javě FX mám teď zásadní výhradu.... a to práce s vlákny.
Nedávno jsem zjistil při doučování, že v javě FX asi nefunguje join, jak
by měl a doteď to hledám. To jen tak pro info, že všechno pro má i své
proti.
díky za typy
dědění rozumím, ale nevím, jak vyřešit čistou metodou toto:
dejme tomu, že je pacman v režimu, kdy honí duchy
duch se to ale musí dozvědět, jinak bude dál pacmana honit
vytvořil jsem si tedy v pacmanovi vlastnost "režim" a veřejnou metodu,
kterou volá duch, aby se dozvěděl,v jakém režimu pacman je, jediné, co se
ale duch dozví o pacmanově režimu, je "null"
vlastnost "režim" mám samozřejmě řádně inicializovanou, a "null" mi to
hází podle mě proto, že se duch ptá na svou poděděnou vlastnost, která
žádnou hodnotu nemá
potřeboval bych do metody nastavil, aby duchovi vracel hodnotu předka (něco
jako "return ancestor.rezim" namísto return rezim)
Nestíham písať z mobilu ale môžeš pozrieť tuto, kde je v podstate riešená tvoja otázka . Vytvoriť si vlastný
listener.
Řešení je vždy několik a musíš vybrat takové, aby odpovídalo právě
tvému projektu.
Nejjednodušší řešení, co může být, tak aby si při vytváření
každého ducha každý duch pamatoval instanci pacmana. Případně by mohla
existovat tovární metoda pacmana, kterou bys vždy zavolal a na ní bys volal
stavovou metodu pacmana, ve které bys zjistil, v jakém stavu pacman je.
Příklad tovární metody:
public class Ghost extends Pacman {
public void move() {
//definition
if(Pacman.getInstance().isDanger()) {
//move from pacman
}
//definition
}
}
Příklad pamatování si instance (což by možná v tomto případě mohlo být i dobrý. Budeš mít jistotu, že pracuješ s konkrétní instancí, takže nebudeš muset řešit problém, když bys spustil novou hru, tak že bys chytal pacmana, který už třeba neexistuje, protože bys ho zapomněl převytvořit:), atd.)
public class Ghost extends Pacman {
private final Pacman pacman;
public Ghost(<barvička>, <pozice>, <další parametry>, Pacman currentPacman) {
pacman = currentPacman;
}
public void move() {
//definition
if(pacman.isDanger() ) {
//move from pacman
}
//definition
}
}
S tím, že Pacman bude v sobě obsahovat metodu isDanger(), která bude kontrolovat, že sežral ten protein a může baštit duchy. No a docela dobré je, že tuto metodu takto dědí i duch. Takže pro pacmana je v případě, že nic nesežral, zase danger duch:) Takže to je právě zajímavý případ, kdy se dědičnost vyplatí jak potomkovi vůči předkovi, tak i dokonce naopak. I když to není samotný smysl dědičnosti, tak teď bys to krásně mohl takhle využít:)
S tím, že pro předka je duch danger, pokud nic nesežral. Pokud sežral, tak je on danger a naopak duch danger není, takže v podstatě v potomkovi převrátíš podmínku. A dokonce by to šlo udělat logičtěji. Že bys v duchovi nic nemusel definovat, ale stačilo by, kdybys v metodě isDanger(), kterou zdědíš z pacmana, tak převrátil výsledek.
@Override
public boolean isDanger() {
return super.isDanger() == true ? false : true;
}
A jsi vysmátý:) a veškerou logiku definuješ v pacmanovi
Ahoj.
Já bych to řešil spíše tak, že bych nejprve deklaroval abstraktní třídu Postava, ze které bych dědil jak třídu Duch, tak třídu Pacman, protože jde o zcela odlišné postavy s odlišným chování. Je sice pravda, že se nám nyní dědění Ducha z Pacmana hodí, ale s dalším vývojem nejspíše narazíme.
public abstract class Postava {
private AbsolutePosition ap;
public void move(AbsolutePosition ap);
public void move(RelativePosition rp);
public Postava(AbsolutePosition ap) {
this.ap = ap;
}
}
Před Duchem deklarujeme Pacmana:
public class Pacman extends Postava {
private protein;
public Pacman(AbsolutePosition ap, Protein protein, etc.)
super(ap);
this.protein = protein;
etc.
}
Dále definujeme Ducha jako potomka Postavy:
public class Duch extends Postava {
private barva;
public Duch(AbsolutePosition ap, Barva barva, etc.)
super(ap);
this.barva = barva;
etc.
}
A takto to rozvíjet. Podle pravidla, že každý rodič může být zastoupen svým potomkem.
Podle pravidla, že každý rodič může být zastoupen svým potomkem
Trošku nelogická věta...
Možná jsi myslel spíš, že potomka můžeme přetypovat na datový typ
rodiče, ale naopak to nejde, protože třída přeci nemůže vědět, kdo z
ní bude dědit. Jediná třída, která to ví, je konečná třída. Z té
nikdo dědit nebude.
Jinak samozřejmě, existuje spousta možností, jak programovat. V tom je právě ta krása a kouzlo. A je jen na programátorovi, jakým směrem se ubere a pro co se rozhodne. Já osobně používám abstraktní třídu jako rozšíření rozhraní, ne dědičnosti. To je každého věc, jak se k danému kódu dohrabe, ale hlavně by měl svůj postup i vždy zdůvodnit - proč to či ono tak je.
Mluví se o tzv. Principu Liskové.
Třeba zde: https://cs.wikipedia.org/…toupen%C3%AD .
Viz. též Rudolf Pecinovský, Java 8.
Ano, protože každý potomek dědí vše od předka a v tom případě může dojít k nahrazení předka potomkem. Obráceně to nejde, protože potomek může mít vlastnosti navíc, které předek nemá.
Právě, že z pohledu OOP objekt datového typu předka přetypovat datovým typem potomka. Aspoň takto ta věta v kontextu vyzněla
Hezky se tu rozvinula debata, ale můj problém moc neřešila.
O společném předku samozřejmě uvažuju, ale učím se to a chci si to
všechno osahat.
Takže info pro vás:
Hledal jsem maximálně jednoduché řešení, které bude zároveň
přirozené. Tj. žádné vytváření dalších objektů či komunikace přes
prostředníka (přes arénu, protože ve třídě Aréna nepotřebuju režimHry
znát)
Řešení Listener už bylo hodně blízko mé představě.
Nakonec jsem to vyřešil úplně jednoduše: třída pacman má seznam všech
objektů (vytvořil jsem si ho), které přes ni byly inicializovány. Vytvořil
jsem si tedy ve zděděné třídě Duch veřejnou metodu
"oznameniZmenyRezimuHry (RezimHry rh)" a z rodičovské třídy Pacman jsem
těm duchům prostě řekl, že je teď bude pacman honit.
Ve hře existují pouze dva stavy - Pacman je pri kolizi schopen zničit ducha ci nikoli. Proč jednoduše nevytvoriš proměnnou pro Pacmana jejíž status 0 nebo 1 odpovídá tomu, zda na Pacmana působí efekt sežráné koule či nikoli?
to ale problém neřeší, taky jsem měl proměnnou, pouze se jinak
jmenovala (režimHry), řešil jsem problém, jak to odkomunikovat a správně
sdělit ostatním objektům
chtěl jsem jenom vědět, jak by spolu měly tyto objekty ve hře komunikovat a
přišel jsem na to:
nejlepší přístup bude přes arénu - ta bude něco jako rozhodčím v
zápase - pacman jí nahlásí sežrání koule a aréna rozhodne o změně
režimu hry - ve fotbale je to stejné, i když přijde faul a půl hřiště je
přesvědčeno o tom, že se bude kopat desítka, tak to musí někdo
posvětit
Ovšem že řeší. Sežrání koule je globální událost která ovlivňuje všechny pohybující se objekty a přenastavuje jejich vlastnosti. Což je jistá forma rozhodčího. Při kolizi dochází k porovnávání vlastnosti objektu, kteří se kolize zůčastnily. Tedy ze správného hlediska můžeš vlastnost ničení přiřadit všem objektům a při vyhodnocování kolize je porovnávat. Ale jelikož víš na základě logiky jak princip kolize probíhá, tak nepotřebuješ duplikovat něco co lze určit přímo. Duplikace vlastnosti je v tomto případě akorát mrhání paměti a procesorového času.
Řekl bych, že zmíněný Listener je dobrý pro situace, kdy je v Aréně s Pacmanem více Duchů.
Listener jsem našel zde: https://cs.wikipedia.org/…ávrhový_vzor)
A co kdyby se změnou stavu hry se změnilo chování Pacmana i Duchů. Například v jednom stavu prchá Pacman, v druhém Duchové, tedy změna jejich chování či algoritmu...
To vše už je zahrnuto v globální události při sežrání koule. Není to jen o nastavení vlastností Pacmana na lovce. Ta událost je komplexni (dochází ke změně chování objektu, jejich barev, zvuků, apod).
Ale ten Observer-Listener se zde uplatní, ne? Abychom nemuseli stále obcházet Pacmana a všechny Duchy. Já bych vložil do List<Duch> (asi ArrayList nebo LinkedList) instance Duchů a jednou za periodu bych je otestoval a eventuálně s nimy o krok pohnul.
To co potřebuješ znát v okamžiku spuštění události je přístup k vlastnostem všem pohybujících se objektů. Ač každý objekt má své vlastnosti, přistupujeme k objektům v teamu neindividualne! V teamu je třeba pracovat úplně jinak. Tak, že společné vlastnosti sdružují do jedne. Tedy nadobjektove. Konkrétní vlastnosti ukazují na jednu nadobjektove, jejíž změna se přenese na vsechny objekty, u kterých se má změna projevit. Úspora procesorového času a paměti tohoto způsobu oproti přístupu ke všem objektům samostatně je extrémní!
Observer-Listener se zde určitě uplatní, informace se k posluchačům
dostane automaticky, takže nemusíš iterovat arraylist, a když máš
arraylist, tak nepotřebuješ observer-listener. Je to v podstatě stejné
řešení, jenom nemusíš používat slovo implement. Ale i v tom listeneru si
vytváříš arraylist posluchačů. takže v mém případě je listener něco
navíc.
Jak jsem již ráno psal, udělal jsem to přes arraylist (kde mám odkazy na
pacmana i duchy). Zprávu posílá pacman duchům samozřejmě jen, když pacman
sežere kouli. Takže duchové nemusí každý cyklus zjišťovat, co se děje.
A nezatěžují tedy ani procesor.
V tomto případě ano, ale pokud bude zadání gradovat, třeba směrem na více druhů nepřátel, s více různými vlastnostmi, tak si myslím, že potom budeš muset provést refaktoring a postupně změnit objektovou strukturu projektu.
Pokud by se ve hře dynamicky měnil počet a typ nepřátel, asi by byl vhodný i klasický Observer-Listener.
Podle Pecinovského by se k výkonové optimalizaci se mělo přikročit až po implementaci problému.
Ještě bych řekl, že později i nepřátelé mohou mít různé chování.
ano, myslím, že máš pravdu, ale i ten počet a typ nepřátel jako programátor znáš, takže je do toho arraylistu můžeš přidat (tj. se sám rozhodneš, jak tu hru postavíš a naprogramuješ), takže asi záleží na programátorovi, jestli ty dynamické změny bude chtít znát, anebo se nechá překvapit (sebe i objekty hry) a použije listener
jde to tedy i přes statiku, jak jsem dnes zjistil, je to pohodlnější než listener i iterování arraylistem, ale tady píšou, že statika je zlo, takže to nepoužiju a nebudu tu psát podrobnosti, dokonce po změně hodnoty proměnné v předkovi se hodnota aktualizuje v potomcích
Pecinovského jsem zatím nečetl, jenom lekci 19 (abstraktní třída) z
těchto stránek, o návrzích taky vím kulové. Já většinou dělám
všechno obráceně. Nejdříve to pochopím tak nějak sám od sebe, a teprve
pak si to ověřuju na základě teorie.
Ze začátku jsem se toho tedy tudíž strašně zalekl, ale když jsem
pochopil, že abstraktní třídu nelze instanciovat, tak mě to navedlo a
přestal jsem Hrac hrac = new Hrac() cpát pořád do třídy Arena . Pak jsem dokonce pochopil, že tam
můžu nechat Pacman pacman i Duch duch a instanciovat, jak jsem to dosud měl.
Nakonec jsem vytvořil na herní aréně nejprve abstraktní třídu ObjektAreny
(tím může být jednak ovoce, které se čas od času objeví, aby ho pacman
mohl sníst a dostat body, jednak pacman, duch apod. - protože jsou to
samostatné objekty a mají společné základní vlastnosti a metody). Tam jsem
nadefinoval, že každý takový objekt tedy musí mít jako atribut jméno a
metody (inicializace časovače, inicializace grafiky a akce, což je metoda
vyvolaná časovačem). Ze třídy ObjektAreny jsem pak vydědil třídu Hrac
(tj. Hráč) - nějak se mi podařilo vytvořit konstruktor a pak už to šlo
jak po másle. Třída Pacman už je potomkem třídy Hrac, jenom třída Duch
je ještě podle třídy Pacman (samotná třída Duch je už hotová, ale v
dalších asi 3 třídách mi to ještě hází chyby - budu tam muset pár
věcí přetypovat a tak podobně), ale ještě tak hodinu a měl bych mít
hotovo. Pak bych výhledově aplikoval ten ActionListener (to se mi podařilo
jen na primitivním příkladu, ale ve třídě Pacman jsem si ani neškrtnul.)
Ovoce by mělo pacmanovi oznámit, že se objevilo/zmizelo. Přijde mi totiž
divný, aby se pacman každý krok dotazoval do arény, jestli se náhodou ovoce
neobjevilo.
Mohl bych si
samozřejmě vytvořit datové pole, tam mít informace o pozicích duchů,
pacmana, ovoce, zdí arény apod., ale to by bylo příliš snadné a už jsem
to tak kdysi dělal. To OOP je fakt dobrý.
Je známá věc, že počítače se učí s počítačem. Nejde jen číst sebelepší knihu. Ale ze své praxe vím, že dobrá kniha je opravdu důležitá. Není dobré vynalézat kolo ani návrhové vzory!
Nejlépe je nastudovat si problém a hned ho zkusit v počítači.
Držím palce.
Podařilo se mi to oddědit, ale bylo to peklo.
Místo ActionListeneru dává ovoce zprávu pacmanovi přes arénu. Výhledově
bych mohl napsat třídu rozhodčí, až si načtu nějakou tu teorii.
Příště už to budu psát od začátku oddělené.
Díky za podporu.
Nemáš zač.
Ještě je dobré zvládnout testování a refaktorování. Testování je jasné, ale také je třeba to umět. A refaktoring se uplatní při změně struktury programu.
Díky
Nakonec jsem se dostal do situace, kdy mi nezbylo než to implementovat, a
podařilo se!
V herní aréně jsem si instancioval ManažeraAudia, jenomže když klip
dohrál, tak jsem neměl (alespoň já jako začátečník to tak cítím) jinou
rozumnou možnost, jak aréně říct z manažeraAudia, že hudba dohrála a že
může spustit hru. Asi jsem to měl udělat jinak (u klipu totiž už
používám LineEvent - LineListener), ale potřeboval jsem rychle funkční
řešení. Funguje to fantasticky.
m.
Zobrazeno 33 zpráv z 33.