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


Předem říkám, že to, co ti teď poradím, tak je v rámci stávajícího kódu!
Řešení by mohlo být využití tvého vlákna, které ti už "tiká".
Nejdřív si ve třídě Hrac vytvoř metody pro posun (včetně proměnné
move):
...
private int move = 10;
...
public void moveRight(int xMove) {
xpozice += xMove;
}
public void moveDown(int yMove) {
ypozice += yMove;
}
public void moveRight() {
moveRight(move);
}
public void moveLeft() {
moveRight(-move);
}
public void moveDown() {
moveDown(move);
}
public void moveUp() {
moveDown(-move);
}
Dále si v hráči připrav metodu, která vrátí aktuální směr. Kupříkladu:
private String way = "right";
...
public String getWay() {
return way;
}
Potom můžeš tyto metody provolat v metodě keyPressed(KeyEvent). S dovolením jsem nahradil ify za switch.
@Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_LEFT:
moveLeft();
way = "left";
break;
case KeyEvent.VK_RIGHT:
moveRight();
way = "right";
break;
case KeyEvent.VK_UP:
moveUp();
way = "up";
break;
case KeyEvent.VK_DOWN:
moveDown();
way = "down";
}
}
Doteď to byla pouze úprava stávajícího kódu. Nyní můžeš tyto metody
i využít v jiných třídách.
Takže ve třídě MainClass2 uprav:
while (this.isRunning) {
long nowTimeCycle = System.nanoTime();
unprocesedTicks += (nowTimeCycle - lastTimeCycle) / Nspertick;
lastTimeCycle = nowTimeCycle;
while (unprocesedTicks >= 1) {
Ticks++;
unprocesedTicks--;
this.update();
}
FPS++;
this.render();
if (System.currentTimeMillis() - lastTimeOutput > 1000) {
lastTimeOutput += 1000;
System.out.println("Ticks: " + Ticks + "," + "Fps: " + FPS);
this.score += 0.5;
FPS = 0;
Ticks = 0;
}
}
tak, že někam do posledního ifu provolej metody, které posunou hlavu.
switch (hrac.getWay()) {
case "right":
hrac.moveRight();
break;
case "left":
hrac.moveLeft();
break;
case "up":
hrac.moveUp();
break;
case "down":
hrac.moveDown();
}
Teď trošku kritiky, kterou doufám využiješ a upravíš si podle toho ten
kód.
Neber to tak, že co ti napíšu já, tak je dogma a že to tak za každých
okolností musí být.
Doufám, že se tu vyjádří spousta jiných programátorů, kteří buď
potvrdí a nebo řeknou něco jiného, než já.
Pozitiva:
- Oceňuji, že jsi všechno nerval do main metody, ale v main metodě jsi jen inicializoval hlavní třídu a pak provolal metodu, kterou jsi i dobře pojmenoval
- Oceňuji snahu používat statickou proměnnou. Tady jsi to použil pro název. Jen pozor - podle konvencí se používá pro názvy tzv. camelCase notace (velbloudí notace). To znamená, že názvy proměnných začínají malým písmenem a pak se používají velká písmena v dalších názvech. Takže ve tvém případě by to bylo místo Game_Title gameTitle.
Podtržítka se v názvech v Javě moc nepoužívají. Maximálně při třídních konstantách (proměnných static final), kde se podle konvencí používá místo camelCase notace UPPER_CASE notace. To znamená všechna písmena velká a právě mezi slovy se používají podtržítka.
Negativa/nedostatky:
- názvy tříd se píší podle konvencí vždy s velkým písmenem (máš tam hrac místo Hrac)
- Názvy tříd jsou strašně matoucí. Máš názvy: balík - hra11, třídy: Hra1, Hrac, MainClass2. To je strašné a naprosto neintuitivní.
Neboj se psát delší názvy, hlavně aby jsi se v nich jako programátor
dobře orientoval. Kupříkladu balíků můžeš mít víc a můžeš se v nich
rozepsat. Například: java.games.development.snake a třídy: MainWindow,
Player, GameProperties nebo tak nějak
Snaž se používat anglické názvy. Už jen proto, aby sis procvičil
angličtinu, ale hlavně proto, že se málokdy používají české názvy.
- main metodu máš ve třídě Hra1 a ne v MainClass? Proč?
- je zbytečné při provolávání metod stejné třídy používat klíčové slovo "this". Jde to, ale je to zbytečné.
- když už máš název v proměnné, tak při jeho používání používej getter (get metodu). Takže by bylo lepší nahradit kód "this.setTitle(Hra1.Game_Title);" kódem "setTitle(getGameTitle());"
Poslední věc - není to negativum, ale není potřeba dědit JFrame. Také
jsem osobně tak začínal, že jsem to zbytečně dědil, ale není to
příliš potřeba, pokud si JFrame nechceš rozšiřovat.
Je lepší si vytvořit v metodě init proměnnou JFrame frame = new JFrame(); a
pak to všechno volat na instanci frame, než to dědit. Pokud to není
skutečně nutné, tak se snaž nepoužívat dědičnost. V Javě lze dědit
pouze z jednoho předka. Proto je dobré si dědičnost šetřit a kde ji
nemusíš používat, tak nepoužívej.
Teď si představ, že bys chtěl stejným způsobem používat vlákna. Místo
rozhraní nebo samostantné implementace třídy Thread bys dědil z této
třídy. Jak bys tam potom dědil kupříkladu třídu JFrame? Nebo v FX bys
nemohl dědit z třídy Application. Proto s tou dědičností raději
šetři.
- používáš příliš moc mezer a kód je nepřehledný. Nevím, jaké používáš IDE, ale nauč se používat tzv. automatické formátování. Vygoogli si, jak se v některém IDE používá. Já osobně se to naučil používat v netbeansech (tam je na to zkratka alt+shift+f). Tuto stejnou zkratku jsem si nastavil pak jak v IDEI, tak v Eclipse.
+20 Zkušeností
+2,50 Kč

Jinak ještě ti řeknu, jak bych tohle programoval asi já.
- Jednak bych si v dnešní době určitě nevybral Swing, ale když už, tak FX (samozřejmě značka ideál je JavaScript, protože ten je dnes víc žádoucí. Od desktopových aplikací se upouští, protože lidé si dnes žádají webové technologie. To je trend. Ale já jsem ten poslední, který ti bude swing vyčítat:) Maximálně bych ti rozmlouval AWT:) )
- Tento příklad je naprosto žádoucí k tomu, aby využil návrhový vzor stav (State).
- Pohyb hlavy by měla komplexně zajišťovat třída Hráč. Takže i posun hlavy by měl být určitě v samostatném vlákně hlavy. Tím by jsi měl i připravenou featuru na to, abys hru případně rozšířil o více hráčů. Tím bude mít každá hlava vždy své samostatné vlákno. Teď to funguje tak, že sice hlava umí pohyb, ale kontinualitu pohybu zajišťuje MainClass. Což je nelogické. Představ si, že v reálném životě bys to měl tak, že ty se sice naučíš používat levou a pravou nohu, ale kdybys měl třeba takové lidi tři, tak by jejich pohyb zajišťovala nějaká společná centrála. No to je přece blbost. Takže vlákno bych použil určitě samostatně ve třídě Hráč.
PS: implementace některých tříd a událostí se dá napsat jednodušeji, než jak to máš ty. Ale to se těžko nachází v tutoriálech. Proto ti dám tady pár typů funkčních kódů, které bys mohl využít.
Pro implementaci KeyListenera nebo MouseListenera, tak Java používá
zajímavou výjimku. Listener = posluchač, což je rozhraní. A samozřejmě,
když implementuješ rozhraní, musíš implementovat veškeré potřebné
metody (což v těchto případech znamená kupříkladu KeyPressed, KeyTyped,
KeyReleased). No ale ne vždy potřebuješ všechny metody. To samé platí i u
mouseListenera.
Proto pro tyto dva případy (keyListener a mouseListener) můžeš využít
tzv. adapter. To je třída, která právě tyto metody obsahuje a ty je
můžeš pouze jenom překrýt. Ale nemusíš mít ve své vlastní třídě
jejich prázdná těla.
prakticky to tedy vypadá následovně (předvedu ti to na JPanelu, ale jde to
na jakékoliv swing componentě):
JPanel panel = new JPanel();
panel.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
}
});
Samozřejmě si můžeš vybrat jakoukoliv z těch možných metod.
Dále if a switch. Místo ifování můžeš používat tzv. switch. To je
syntaxe pro výčet možných případů. Zatím funguje pouze pro int a
String!! Důležité vědět.
Zápis je takový, že napíšeš klíčové slovo switch a do závorky
napíšeš hodnotu, kterou budeš sledovat. Například nějakou proměnnou, do
které budeš získávat nějaké číslo/String od jinud.
Dále v těle switche bude vždy každá položka vyhrazená klíčovým slovem
"case". Následuje mezera a potom hodnota, která může nastat (String v
uvozovkách a nebo číslo). Potom následuje dvojtečka. Pak si můžeš napsat
sadu kódu, které se mají provést při této možnosti.
A na konec každé položky musíš z tohoto switche vystoupit ven pomocí
klíčového slova "break". (stejně tak, jak se vychází z cyklu, když jej
chceš v kódu za podmínky ukončit). Pokud nenapíšeš slovo break, tak bude
switch procházet následující položky až do konce. Samozřejmě díky této
informaci můžeš odvodit, že u poslední položky není break třeba. Tam se
to prostě ukončí a hotovo.
Ještě pro zajímavost - pokud bys chtěl napsat kód pro všechny ostatní
položky, které neodpovídají těm, které jsi vypsal, tak se na to používá
klíčové slovo default. Konvence určuje, že se defaultní hodnota píše
nakonec switche.
příklad:
private int pocetLidi = 0; //a tuto hodnotu si někde získáš. Z konzole nebo z nějakého posluchače
...
switch(pocetLidi) {
case 5: System.out.println("nic moc, jenom 5 lidí");break;
case 10: System.out.println("To už je lepší, máme deset lidí");break;
case 15: //chybí break, takže pokud zadáš 15 nebo 20, tak se provede kód pro case 20
case 20: System.out.println("Máme 15 nebo 20 lidí");break;
default: System.out.println("Musíte zadat 5, 10, 15 nebo 20");
}
Tento kód tedy znamená, že když zadáš položky 5 nebo 10, tak se vypíše nějaký text, když zadáš 15 nebo 20, tak se vypíše třetí text (stejný pro obě možnosti) a když zadáš jakékoliv jiné číslo, tak se vypíše defaultní zpráva.
Vlákno můžeš implementovat třemi možnostmi.
- děděním třídy Thread (class extends Thread. U této možnosti musíš implementovat metodu run()).
- implementováním rozhraní (class implements Runnable. Potom musíš jednak: definovat metodu start a definovat metodu run()).
- implementovat samostatné vlákno (vytvoříš samostatné vlákno a metodu run implementuješ jako anonymní metodu).
Tady máš ukázku všech tří případů:
public class Vlakno extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Vlakno vlakno = new Vlakno();
vlakno.start();
}
}
public class Vlakno implements Runnable {
private Thread thread;
@Override
public void start() {
if (thread == null || !thread.isAlive()) {
thread = new Thread(this);
thread.start();
}
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Vlakno vlakno = new Vlakno();
vlakno.start();
}
}
public class Vlakno {
public Vlakno() {
new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
public static void main(String[] args) {
new Vlakno();
}
}
S tím, že ten poslední kód by se dal zapsat i takhle, pokud netušíš, co je lambda (ta šipka v kódu). + ukázka, že to jde napsat i odděleně a metodu start můžeš napsat na proměnnou Thread.
public class Vlakno {
public Vlakno() {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread.start();
}
public static void main(String[] args) {
new Vlakno();
}
}
Vidíš sám, že ten třetí případ je nejstručnější a vcelku praktický. Musíš prostě vědět, kdy co chceš použít a jestli má význam implementovat to či ono rozhraní či dědit třídu. Na takových věcech se pozná skill programátora. Měj na paměti, že i když to jde, tak ne vždy je to výhoda.
No a poslední, co ti ukážu, tak velmi stručné a funkční použití
swingu pro jednoduché vykreslování. Není potřeba používat canvas, není
potřeba dědit a hlavně neboj se používat panely. U swingu se panely
používaly pro seskupení komponent do společných bloků. Díky tomu se potom
vše lépe vykreslovalo a projekt byl více objektový, protože se můžeš
soustředit na jeden úsek programu a nemusíš se bát, že rozbiješ něco
druhého.
Proto teorie swingu je:
JFrame -> na něj základní JPanel s nějakým rozložením (layoutem) a
potom pro každý blok použít samostatný JPanel (klidně s dalším
rozložením) a pak až nakonec používat JComponenty (tlačítko, radio
tlačítko, slider, atd. atd.)
Samozřejmě těch panelů může být neomezeně a čím víc si to zaobalíš
do panelů a utvoříš tak pomyslné oblasti, tak tím lépe se v tom budeš
orientovat.
To byla hlavní myšlenka swingu. JavaFX je založená na úplně jiné
koncepci.
No a závěrem - každá componenta ve swingu musí být vykreslená (proto
každá componenta obsahuje metody paint, paintComponent atd. A v této metodě
se pak vykresluje potřebný objekt (čtverec, kruh, polygon, obrázek, atd.
atd. atd.)
Takže úplně jednoduchou hlavu do Framu bych udělal asi takhle:
package development.games.snake;
import javax.swing.*;
import java.awt.*;
import java.util.Random;
public class Game {
private final int componentSize = 20;
private final Random random = new Random();
private JPanel panel;
public Game() {
JFrame frame = new JFrame();
frame.setSize(1000, 800);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setTitle("Nějaký nápis");
panel = new JPanel();
panel.setLayout(null);
frame.add(panel);
panel.setBackground(Color.GRAY);
frame.setVisible(true);
Player head = new Player();
head.setSize(componentSize, componentSize);
head.setLocation(panel.getWidth() / 2 - head.getWidth() / 2, panel.getHeight() / 2 - head.getHeight() / 2);
panel.add(head);
head.start();
head.requestFocus();
}
public static void main(String[] args) {
new Game();
}
}
package development.games.snake;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
public class Player extends JComponent implements Runnable {
private final int move = 1;
private Thread thread;
private int xMove;
private int yMove;
public Player() {
addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
setXMove(0);
setYMove(0);
switch (e.getKeyCode()) {
case 38:
moveUp();
break;
case 37:
moveLeft();
break;
case 40:
moveDown();
break;
case 39:
moveRight();
}
}
});
}
public int getXMove() {
return xMove;
}
public void setXMove(int xMove) {
this.xMove = xMove;
}
public int getYMove() {
return yMove;
}
public void setYMove(int yMove) {
this.yMove = yMove;
}
@Override
public void paint(Graphics g) {
g.setColor(Color.YELLOW);
g.fillOval(0, 0, getWidth(), getHeight());
getParent().repaint();
}
@Override
public void run() {
while (true) {
if (getX() + getXMove() > 0 && getX() + getWidth() + getXMove() < getParent().getWidth() &&
getY() + getYMove() > 0 && getY() + getHeight() + getYMove() < getParent().getHeight())
setLocation(getX() + getXMove(), getY() + getYMove());
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void start() {
if (thread == null || !thread.isAlive()) {
thread = new Thread(this);
thread.start();
}
}
public void moveUp() {
setYMove(-move);
}
public void moveDown() {
setYMove(move);
}
public void moveRight() {
setXMove(move);
}
public void moveLeft() {
setXMove(-move);
}
}
petr:2.2.2020 19:26
WOW Tak to jsem
nečekal, že se tu někdo až tak rozepíše
Určitě se zaměřím na své
chyby nebo nedokonalosti kódů
, já nejsem moc pokročilý programátor, ale snažím se to
zlepšovat. Děkuju.
Zobrazeno 5 zpráv z 5.