# bitte um performance ratschläge



## hdi (18. Feb 2008)

hallo,

nach 3 verschiedenen versuchen hab ich noch immer kein richtig schön flüssiges, vorallem stabil flüssiges, spiel hinbekommen (geht um n tetris).

ich hab hier grad n riesen aufsatz geschrieben wie ich das gemacht habe, aber wieder gelöscht. ich denke das würde euch eher langweilen und ohne viel code steigt man da eh nicht durch.

also frag ich gleich mal heraus, wie ihr den spielablauf von einem tetris-spiel gestalten würdet.

also: wieviele threads? (z.B. braucht man einen der alle 500ms einen stein nach unten rückt, aber einen anderen der in viel kürzeren abständen die tastatur überwacht)

am meisten geht es mir aber darum, wo ich die hauptberechnungen reinpacken soll.

direkt in den keylistener? und mit einem SwingWorker? Ich weis snciht, ich hab lediglich ein video gesehen vor nem tag wo jemand über swing geredet hat und darüber, komplexe vorgänge eines listeners in einen SwingWorker zu packen, weil sonst die repaint-methode zu lange warten muss.

ja also ich könnt wieder ewig labern  

wäre einfach sehr froh wenn mir einer einen logischen grundriss von tetris gibt, nur im hinblick auf threading.
welcher thread am besten was übernimmt, wie sie zusammenhängen und wo gezeichnet wird.

weil ich kriegs einfach nach 100 anläufen nicht richtig hin, und ich hab es auch satt code zu schreiben, bei dem ich schon während des schreibens die zähne zusammenbeiss so vonwegen "hoffentlich klappts".. das is ja nix, ich mag das _endlich _mal *kapiert* haben - tu schon seit über 3 wochen jeden tag 5 stunden mit swing rum  :### aber bisher hab ich immer nur codefetzen von hier abgeschrieben oder halt durch rumprobieren gecodet.

also wär sehr dankbar, mich interessiert es einfach wie ihr profis so ein spiel angehen würdet! ich wette ich mach das absolut verkehrt weil es auch unnötig kompliziert und verschachtelt is.


----------



## Wildcard (18. Feb 2008)

2 Threads.
Ein 'Mover' Thread und der AWT Event Dispatch Thread der die Sache zeichnet.


----------



## Guest (18. Feb 2008)

hm also so einfach isses glaub ich nicht.
wie gesagt, man kann nicht einne mover-thread machen, denn es muss jede halbe sekunde der block eins nach unten ruschten (sleep 500), aber nach links und rechts bewegen und den Block drehen kann man natürlich mehr als nur 1 mal in der halben sekunde, das kann man viel schneller machen (sleep 20)

d.h. dafür alleine brauch ich schon zwei threads, oder?

und was meinst du mit dem AWT event dispatch der das zeichnet? was isn der AWT e.d. genau, das is ja nix was ich neu anlege oder? wie kann ich den zeichnen lassen?


----------



## Marco13 (18. Feb 2008)

Das Hin-und-Her-Bewegen wird ja sowieso über KeyEvents gemacht. Die Information wird dann einfach (direkt, oder indirekt) auf den Stein übertragen. (Du wirst ja jetzt wohl auch nicht alle 20 ms abfragen, ob irgendeine Taste gedrückt wurde?)
Wozu man da mehr als einen Spiel-Thread benötigen sollte (und den EDT) wüßt' ich jetzt auch nicht...  ???:L


----------



## Guest (18. Feb 2008)

> Du wirst ja jetzt wohl auch nicht alle 20 ms abfragen, ob irgendeine Taste gedrückt wurde?



ja gut äh, also ich sag mal, naja, natürlich isses ja so äh, und vorallem DESHALB, und ja genau (shice  )

jaaa also irgendwie passieren die üänderungen auf der datenstruktur eines blocks gar nicht im keylistener, sondern eben in einem andren thread.
mein keylistener macht im moment nix anderes als eine variable zu setzen, in der die gedrückte taste gepsiechert wird.

jo, das is wohl nich so gut was^^ ich sollte also die berechnungen DIREKT in die methode keyPressed() reintun?


----------



## Wildcard (18. Feb 2008)

Anonymous hat gesagt.:
			
		

> jo, das is wohl nich so gut was^^ ich sollte also die berechnungen DIREKT in die methode keyPressed() reintun?


Bei den meisten Spielen wäre das nicht der Renner, zu Tetris passt es aber ganz gut, da die Geschwindigkeit tatsächlich von der Häufigkeit der Tastenanschläge abhängt.


----------



## Marco13 (18. Feb 2008)

Die Berechnungen sollten nicht unbedingt in die KeyPressed (genaugenommen hätte ich da sogar gewissen Bedenken bzgl. Synchronisation). Dass das keyPressed nur eine Variable setzt, wo drinsteht, welche Taste gedrückt wurde, KÖNNTE auch OK sein. Aber dann sollte das KeyPressed dafür sorgen, dass der Spiel-Thread aufgeweckt wird, und sich die Taste abholt. Also, wenn der Stein in 500ms-Schritten fallen soll, dann könnte da ja evtl. irgendwo ein 
wait(timeTillNextStep);
stehen, wobei timeTillNextStep eben anfangs 500 ist. Wenn eine Taste gedrückt wird, wird aber der Thread ge-notify()-t, holt sich die gedrückte Taste ab, macht die bewegung, und wartet dann (mit wait) die restliche Zeit bis zum nächsten Schritt.
Das sind aber KEINE Empfehlungen! (ich weiß nicht, welche Methode "die beste" wäre). Nur Vorschläge, die du in Erwägung ziehen kannst.


----------



## André Uhres (18. Feb 2008)

Marco13 hat gesagt.:
			
		

> Die Berechnungen sollten nicht unbedingt in die KeyPressed (genaugenommen hätte ich da sogar gewissen Bedenken bzgl. Synchronisation)..


Man muss nur die Eventhandler synchronized machen (Key, Action und Timer Event) :wink:


----------



## Guest (18. Feb 2008)

eventhandler? und das heisst?
nicht die methoden zb. keyPressed() oder?

meinst du sowas:

keyPressed(KeyEvent e){
synchronized(e){
..
}
}

?


----------



## André Uhres (19. Feb 2008)

Beispiel:

```
public class Game {
...
    private synchronized void handleTimerEvent() {
...
    }
    private synchronized void handleActionEvent() {
...
    }
    private synchronized void handleKeyEvent(KeyEvent e) {
...
    }
...
}
```
"handleKeyEvent" z.B. würdest du dann einfach in "keyPressed" aufrufen.


----------



## Guest (19. Feb 2008)

okay..

aber wird nich jedesmal ein neues event-objekt erstellt? heisst das jetzt echt dass eine gedrückte taste erst alle operationen durchführt, bevor eine andere anfangen kann?

und generell: diese sache mit time event und so, nur damit ich weiss woran ich bin:

wenn mein thread der diesen keyListener implementiert immer für 500ms schläft, heisst das dann, dass er in diesen pausen auch nicht auf tastendrücke reagieren wird? anders: MUSS ich dieses ge-notify()-ing (das solltest du übrigens copyrighten() ),  machen, damit der spieler den stein schnell drehen und bewegen kann, während er langsam runterfällt?

danke


----------



## André Uhres (19. Feb 2008)

Anonymous hat gesagt.:
			
		

> ..heisst das jetzt echt dass eine gedrückte taste erst alle operationen durchführt, bevor eine andere anfangen kann?


Das ist ja der Sinn der Synchronisation, nicht wahr?



			
				Anonymous hat gesagt.:
			
		

> ..wenn mein thread der diesen keyListener implementiert immer für 500ms schläft, heisst das dann, dass er in diesen pausen auch nicht auf tastendrücke reagieren wird?


Der KeyListener läuft auf dem EDT und hat nix mit dem Game Thread zu tun. 
Den sleep darf man  natürlich nicht synchronisieren. In diesen Pausen wird dann weiterhin auf Tastendrücke reagiert. 



			
				Anonymous hat gesagt.:
			
		

> anders: MUSS ich dieses ge-notify()-ing (das solltest du übrigens copyrighten() ),  machen, damit der spieler den stein schnell drehen und bewegen kann, während er langsam runterfällt?


Musst du nicht, ist auch nicht meine Idee


----------



## Guest (20. Feb 2008)

hm okay leute also... leider ist das nicht so simpel wie ihr es hier darstellt. nicht umsonst hab ich das jetzt
schon 4 mal gemacht und komm immer wieder in die selben Probleme rein, deren Lösung ich noch immer nicht kenne.

mit nem move-thread und nem keylistener, der "eh den rest macht", geht das nicht. ODer ich kanns nicht. z.B.:

1. Der Spieler droppt nen Stein. der move-thread pennt gerade. Der neue stein hängt entweder 499 ms in der Luft (er wurde erstellt 1ms bevor der move-thread sich schlafen legte), ODER er bewegt sich sofort (der move-thread wacht just in dem moment auf, wo der neue stein erstellt wurde).
und nu? notify()? wait()? hä? synchronized? was? was hat das alles damit zu tun? ICh kann meinen Thread nich aufwecken, super.

2. wenn ne reihe voll ist soll sie aufblinken. dabei soll der move-thread natürlich NICHT nen stein nach unten bewgen! das gleiche problem wieder: was soll ich notifyen? Ich hab kein Objekt für die _Situation _, dass eine Reihe blinkt. Das Blinken einer Reihe ist eine Operation, kein Objekt. Aber normale Befehle gibt es in JAva nicht. Kein Objekt, kein Programm.

usw..

das Problem ist, wie ich schon sagte, dass alles voneinander abhängt! move-thread und jemand, der das zeichnet ist VIEL zu vereinfacht gesagt, so ist einfach das Tetris spiel nicht! Die 2 Punkte waren jetzt nur 2 von 10 Standard-Features eines einfachen Tetris.

Und meine Frage war ja eben, wie ich Threads kommunizieren lassen soll, wie ich das aufbau, usw.
Ich hab das Spiel nun zum 4.mal neu programmiert, und bleibe immer wieder stecken an solchen dingen wie oben erwähnt. 

Riesen Probleme mit Threads halt... Ich bitte euch, nochmal genau über das Tetris-Spiel nachzudenken, und dann noch mal zu überlegen, wie ihr das machen würdet.

Ich will doch nur nen Grundriss eines UML-Diagramms, einfach wieveil Threads es gibt und was die machen.
und zwar so, dass z.B. die oberen 2 Punkte berückstigt werden.

Sorry, ich glaube einfach nicht, dass es so geht wie ihr hier sagt. Und ich werd langsam crazy dass ich so normale Spielsituationen nicht steuern kann,und es dauert nich mehr lang und ich klatsch java an die wand weil einem so krass die hände genbunden sind..

bitte... wie löst ihr sowas in java? ihr könnt das doch (und ich weiss sehr wohl dass JAva das AUCH kann). Aber ich nicht, nicht so wie ich's im Moment versuch. Da muss mehr sein, mehr Threads, mehr Objekte, keine Ahnung. Es geht einfach nich so leicht.

Bidde.. nochmal etwas länger nachdenken und mir weiterhelfen, ich flipp hier aus :bahnhof: 

*vielen*, vielen dank

Und das hier ist jetzt nur für den fall, dass ich mich falsch ausdrück und ich lieber den Code sprechen lasse.
es soll hier aber *NICHT* eure aufbaue sein, den code durchzuwühlen. Nur auf freiwilliger Basis überfliegen, wenn ihr nicht so genau wisst wo überhaupt mein Problem liegt. Sonst ignorieren  Danke.



```
package newtetris;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import javax.swing.border.BevelBorder;
import javax.swing.border.SoftBevelBorder;

public class ActionPanel extends JPanel implements Runnable, KeyListener{
    
    private Collection c; // das ist eine ArrayList mit Blöcken, die im Spiel sind
    
    public ActionPanel(){
        setPreferredSize(new Dimension(300,600));
        setBorder(new SoftBevelBorder(BevelBorder.LOWERED));
        setBackground(Color.gray);

        c = new Collection(10*20);
        c.addElement(Param.now); // Param.now ist ein Tetris-Element, z.B. ein Würfel
        
        addKeyListener(this);
        setFocusable(true);

    }

    public void run() {
        
        while (true) {

            if (Param.running) { // man kann das spiel auch pausieren, dann ist das hier false,
                                          // aber man solls ja auch wieder aufnehmen können, daher steckt
                                          // das ganze in einer while(true)-schleife
                
                try {
                    Thread.sleep(Param.speed); // fall-geschwindigkeit der Blöcke, z.b: 500
                } catch (InterruptedException ex) {
                }

                if (! Param.now.isDumped()) { // das ist der fall wenn das element am Boden angelangt ist,
                                                            // oder mit einem anderen Objekt kollidierte.
                                                            // Hier kommt man also rein, wenn das NICHT der fall ist
                    if (c.mayFall()) {
                        Param.now.move(Param.DOWN);
                    } else {
                        Param.now.setDumped(); // block ist abgelegt worden
                        int multiply = c.checkForLines();  // prüft ob es volle Linien im Spiefeld gibt

                        Param.score += (multiply * 50) * Param.level * multiply;
                    }
                }
                if (Param.now.isDumped()) {  // block ist gerade abgelegt worden,
                    Param.now = Param.next;  // das ganze hier erstellt ein neues Tetris-Element
                    c.addElement(Param.now);
                    Param.next = new Quad();
                }
            }
            repaint();
        }
    }

// diese sachen hier machen so in etwa das selbe, bewegen also blöcke und prüfen ob ein block

    public void keyPressed(KeyEvent k) {
        switch(k.getKeyCode()){
            
            case KeyEvent.VK_LEFT:
                new SwingWorker() {

                    @Override
                    protected Object doInBackground() throws Exception {
                        if(!Param.now.isDumped() && c.mayMoveLeft()){
                            Param.now.move(Param.LEFT);
                        }
                        repaint();
                        return Param.now;
                    }
                }.execute();
                break;

            case KeyEvent.VK_RIGHT:
                new SwingWorker() {

                    @Override
                    protected Object doInBackground() throws Exception {
                        if(!Param.now.isDumped() && c.mayMoveRight()){
                            Param.now.move(Param.RIGHT);
                        }
                        repaint();
                        return Param.now;
                    }
                }.execute();
                break;
                
            case KeyEvent.VK_DOWN:
                new SwingWorker() {

                    @Override
                    protected Object doInBackground() throws Exception {
                        int dropheight = 0;
                        while (!Param.now.isDumped() && c.mayFall()) {
                            Param.now.move(Param.DOWN);
                            dropheight++;
                        }

                        Param.now.setDumped();
                        Param.now = Param.next;
                        c.addElement(Param.now);
                        Param.next = new Quad();
                        
                        int multiply = c.checkForLines();
                        
                        Param.score += dropheight * Param.level +
                                   (multiply*50) * Param.level * multiply;
                        repaint();
                        systimeAtNewObj = System.currentTimeMillis();
                        return Param.now;
                    }
                }.execute();
                break;
                
            case KeyEvent.VK_UP:
                new SwingWorker() {

                    @Override
                    protected Object doInBackground() throws Exception {
                        if(!Param.now.isDumped() && c.mayRotate()){
                            Param.now.rotate();
                        }
                        repaint();
                        return Param.now;
                    }
                }.execute();
                break;
        }
    }
    public void keyTyped(KeyEvent k) {}
    public void keyReleased(KeyEvent k) {}
    
    @Override
    protected void paintComponent(Graphics g){
        super.paintComponent(g);
        c.paint(g);
        new PaintGridLines(g);
    }
}
```

tja, und genau diese sache: der listener und der move-thread, kann ich nicht vernünftig steuern, also voneinander abhängig machen, sie kommunizierne lassen etc.


----------



## schalentier (20. Feb 2008)

Vergiss die Threads. Du brauchst sie nicht. Mach einen Mainloop, in dem passiert folgendes:


```
long lastTime = System.currentMillis();
while( !stopped ) {
   long currentTime = System.currentMillis();
   long delta = currentTime - lastTime;
   lastTime = currentTime;

   processInput( delta );
   applyPhysic( delta );
   render( delta );

   sleep(1);
}
```

processInput( long) ermittelt die aktuelle Eingabe und bewegt darauf hin die Objekte. Wie das genau ablaeuft sei dir ueberlassen. Sinnvoll ist IMHO eine Queue, in die Events reingesteckt werden, z.b. vom Netzwerk oder eben vom User in deiner keyPressed(). Die processInput wuerde dann die Queue abarbeiten, bis sie leer ist und alles der Reihe nach umsetzen.

applyPhysic wendet die "Physik" auf die Spielwelt an. Bei dir waere das z.b. Kollision (Stein zu weit links/rechts) und das Bewegen nach unten. Dazu wuerde ich einen Counter hernehmen, der um "delta" reduziert wird. Ist der Counter kleiner 0, wird er wieder auf die aktuelle Geschwindigkeit gesetzt und der Stein bewegt sich eine Zeile nach unten. 

In render wird dein Frame neu gezeichnet (repaint). Dort ueberschreibste die paintComponent()-Methode, die die ganze Welt zeichnet. Mit welchen Techniken du das tust, sei ebenfalls dir ueberlassen (Doublebuffer, etc). 

Das sleep am Ende verhindert, dass dein Spiel mit 100% Prozessorauslastung laeuft.

Keep it simple!


----------



## byte (20. Feb 2008)

Die Programmlogik im EDT laufen lassen? Ganz schlecht.


----------



## schalentier (20. Feb 2008)

byto hat gesagt.:
			
		

> Die Programmlogik im EDT laufen lassen? Ganz schlecht.



Nix EDT. Das laeuft im Mainthread, also in dem, der automatisch vom main(...) erzeugt wird. Im EDT wird nur gemalt.


----------



## Guest (20. Feb 2008)

hey, also erstmal vielen dank dafür!

ich habe nun mein programm komplett überarbeitet, hab das hier von dir leider erst jetzt gelesen. das heisst ich hab das jetzt nicht berücksichtigt.

okay im moment denke ich aber, es ist der beste anlauf bisher.

heisst: mein move-thread, der die blöcke stück für stück nach unten bewegt, läuft gut, und er kümmert sich komplett um das repainten, ganz alleine, und er kann auch volle lines aufleuchten lassen und dann entfernen. und erst danach kommt ein neuer block. es ruckelt nix, es kommt nich so hängern und alles wird genau in der reihenfolge gemacht, wie es soll.

soweit sogut also.

allerdings fehlt mir nun noch der user-input. und genau hier krieg ichs wieder mit der angst zu tun 
ich möchte nicht Wiiieeeeder, dass der ganze Code kaputt geht durch unüberlegtes coden..

ich möchte euch bitten, die situation, wie sie nun ist, anzusehen, und mir eine gute Lösung empfehlen, weil daran scheiter ich halt immer :/

die unwichtigen dinge hab ich weggelassen (so wie oben z.B. der grund, warum das ein thread ist), ihr seht hier lediglich den hauptcode, der für alles verantwortlich ist, mit kommentaren dazu. Happy hunting 


```
package newtetris;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JPanel;
import javax.swing.border.BevelBorder;
import javax.swing.border.SoftBevelBorder;

public class ActionPanel extends JPanel implements Runnable, KeyListener {

    private Collection c;  // eine ArrayList, die alle _Steine_ enthält, die im Spiel sind.
                                  // ein Stein ist dabei nur ein Rechteck, heisst jeder SpielBLOCK besteht aus 4 Steinen.
    private InfoPanel ip; //unwichtig, was das macht

    public ActionPanel(InfoPanel ip, Collection c) {
        setPreferredSize(new Dimension(300, 600));
        setBorder(new SoftBevelBorder(BevelBorder.LOWERED));
        setBackground(Color.gray);
        setFocusable(true);
        requestFocusInWindow();

        this.c = c;
        this.ip = ip;
        addKeyListener(this);
    }

    public void run() {

        while (true) {

            if (Param.running) { // spiel muss pausiert und gestartet werden können, daher auch die while(true) schleife

              // OKAY HIER BEGINNT DER ERWÄHNTE MOVE-THREAD:
                try {
                    Thread.sleep(Param.speed); // die geschwindigkeit, in der die blöcke fallen
                } catch (InterruptedException ex) {
                }

                if (!Param.now.isDumped()) { // "dumped" ist ein Block, wenn er am untersten Rand ankommt oder
                                                           // auf einen anderen Block trifft. Das folgende wird also bearbeitet, 
                                                           // wenn der Block NICHT dumped ist.
                                                            // Param.now ist der aktuelle Spielblock

                   // die namen der methoden sagen denke ich aus, was gemacht wird.
                   // was man wissen sollte: jede Methode, die auf die collection "c" angewandt wird, 
                   // arbeitet mit dem momentanen Stein. also c.mayFall() überprüft z.B., ob der momentane Stein
                   // fallen darf. 

                    if (c.mayFall()) {
                        Param.now.move(Param.DOWN);
                    } else {
                        Param.now.setDumped();
                        Param.now = Param.next;  // wurde vorher schon erstellt, der nächste Spielblock
                        c.addElement(Param.now); // fügt alle Steine des Blocks Param.now zur Collection hinzu
                        Param.next = Generator.create();  // macht n neuen Zufallsblock im Spiel

                       // Prüft nun, ob eine Zeile komplett ausgefüllt mit Blöcken ist. Wenn ja, werden die entsprechenden
                       // Zeilen aufblinken und dann entfernt (das macht checkForLines, ist jetz unwichtig wie genau)
                        Param.updateGame(0, flashLines(c.checkForLines()));

                        if (c.checkForGameOver()) {
                            Param.running = false;
                            Param.saveHighscore();
                        }
                        // das is nur für die anzeige der punkte usw.
                        ip.update();
                    }
                }
                repaint();
            }
        }
    }

     // oookay und DAS FEHLT JETZT NOCH! 
     // mit links und rechts kann man den stein bewegen, das trickreichste ist eher wenn man nach unten drückt:
     // dann soll der Stein "gedroppt" werden, also solange nach unten fallen (natürlich in cpu-zeit, für den spieler
     // nicht sichtbar), bis er dumped. Und hier ist auch das Problem:
     // Der Listener muss im Prinzip die selben Methoden der Collection aufrufen wie shco der move-Thread.
     // Und da passieren dann eben diese Dinge, dass es nicht mehr regelmässig ist, weil es ab hier parallel läuft.
     // Ich komm nicht dahinter, wie ich das gescheit steuern kann :/

    public void keyPressed(KeyEvent k) {
        switch (k.getKeyCode()) {

            case KeyEvent.VK_LEFT:
                         // ???
                break;

            case KeyEvent.VK_RIGHT:
                         // ???
                break;

            case KeyEvent.VK_DOWN:
                         // ???
                break;

            case KeyEvent.VK_UP:
                         // ???
                break;
        }
    }

    public void keyTyped(KeyEvent k) {
    }

    public void keyReleased(KeyEvent k) {
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        c.paint(g);
        new PaintGridLines(g); // is wurscht hier
    }

   // wird ja vom move-thread aufgerufen. falls es lines zu removen gibt, blinken sie und danach geht das spiel weiter
    public int flashLines(int[] a) {
        if (a.length > 0) {
            int flashTime = 2;
            while (--flashTime >= 0) {

                for (int i = 0; i < a.length; i++) {
                    c.lightUpLine(i);
                }
                repaint();

                try {
                    Thread.sleep(250);
                } catch (InterruptedException e) {
                }

                for (int i = 0; i < a.length; i++) {
                    c.lightDownLine(i);
                }
                repaint();

                try {
                    Thread.sleep(250);
                } catch (InterruptedException e) {
                }
            }

        }
        return a.length;
    }
}
```

ja wie gesagt, nur der move-thread alleine ist ja nicht schwer, weil ich als programmierer weiss, was er wann macht.
wenn jetzt der keylistener dazu kommt hab ich keine Kontrolle mehr über den Ablauf.
Das gibt Probleme, ich kann z.B. die Linien nicht mehr aufblinken lassen und erst DANN einen neuen stein machen,
weil der keyListener die Linien blinken lässt, und schläft (die sleep-aufrufe in der methode flashLines), jedoch der move-thread damit nix am hut hat und fleissig weiter macht..

Das sind die Sachen, die ich halt meinte. Wie steuer ich das, wie kontrolliert man MultiThreading ?

Vielen Dank, ich kanns nciht oft genug sagen.


----------



## byte (20. Feb 2008)

schalentier hat gesagt.:
			
		

> byto hat gesagt.:
> 
> 
> 
> ...



Warum schreibst du dann:


> Vergiss die Threads. Du brauchst sie nicht.


:?: 

Auch in Deinem Code musst Du synchronisieren. Swing ist schließlich nicht thread-safe.

Im übrigen bezweifel ich, dass das Spiel flüssig laufen wird, wenn Eingabe, Berechnung und Painting sequentiell abgearbeitet werden.


----------



## André Uhres (20. Feb 2008)

Anonymous hat gesagt.:
			
		

> ..leider ist das nicht so simpel wie ihr es hier darstellt..


Doch:

```
package demo;

/*
 * Game.java
 */
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Game extends JFrame {

    private JButton btControl;
    private Board board;
    private Figure figure;
    private GameThread thread;

    public Game() {
        super("Start and then type right/left arrows");
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setSize(400, 700);
        setLocationRelativeTo(null);
        
        btControl = new JButton("Start");
        board = new Board();
        
        btControl.setFocusable(false);
        add(btControl, BorderLayout.PAGE_START);
        board.setFocusable(true);
        add(board);
        btControl.addActionListener(new ActionListener() {

            public void actionPerformed(final ActionEvent evt) {
                handleActionEvent(evt);
            }
        });
        board.addKeyListener(new KeyAdapter() {

            @Override
            public void keyPressed(final KeyEvent e) {
                handleKeyEvent(e);
            }
        });
    }

    private synchronized void handleActionEvent(final ActionEvent evt) {
        thread = new GameThread();
        thread.startGame();
    }

    private synchronized void handleTimerEvent() {
        if (figure == null) {
            figure = new Figure(board);
        } else {
            figure.moveDown();
        }
    }

    private synchronized void handleKeyEvent(final KeyEvent e) {
        if (figure == null) {
            return;
        }
        switch (e.getKeyCode()) {

            case KeyEvent.VK_LEFT:
                figure.moveLeft();
                break;

            case KeyEvent.VK_RIGHT:
                figure.moveRight();
                break;
        }
    }

    public static void main(final String args[]) {
        EventQueue.invokeLater(new Runnable() {

            public void run() {
                new Game().setVisible(true);
            }
        });
    }

    private class GameThread extends Thread {

        private int sleeptime = 500;

        public void startGame() {
            if (!isAlive()) {
                this.start();
            }
        }

        @Override
        public void run() {
            while (thread == this) {
                handleTimerEvent();
                try {
                    Thread.sleep(sleeptime);
                } catch (InterruptedException e) {
                }

            }
        }
    }
}

class Figure {

    private int xPos = 100;
    private int yPos;
    private Board board;

    public Figure(final Board board) {
        this.board = board;
    }

    public void moveDown() {
        if (board != null) {
            yPos += 20;
            board.setPos(xPos, yPos);
        }
    }

    public void moveLeft() {
        if (board != null) {
            xPos -= 20;
            board.setPos(xPos, yPos);
        }
    }

    public void moveRight() {
        if (board != null) {
            xPos += 20;
            board.setPos(xPos, yPos);
        }
    }
}

class Board extends JPanel {

    private int x1,  y1;

    public void setPos(final int x, final int y) {
        x1 = x;
        y1 = y;
        repaint();
    }

    @Override
    protected void paintComponent(final Graphics g) {
        super.paintComponent(g);
        g.drawRect(x1, y1, 20, 20);
    }
}
```


----------



## Guest (20. Feb 2008)

Andrés,

ich schätze deine Bemühungen, aber ich denke du hast nicht ganz verstanden:
Deine run-methode, die den Block alle 500ms nach unten schiebt, macht nichts weiter als genau das.

das ist im Spiel aber nicht der Fall. siehe mein code. es muss einiges abgefragt werden, sowohl in dieser run-methode als auch bei jedem keyEvent. und genau dort liegt das Problem.

In deinem Spiel bewegt sich ein Block nach unten, wahlweise auch nach rechts oder links. Das war's. 

In einem Tetris spiel aber bewegt sich ein Block nur solange nach unten, bis er auf etwas stösst, das gleich emuss bei den Richtungen überprüft werden, und es ist erst zur Laufzeit vom Spieler festgelegt, ob der move-thread den Block absetzt oder die nach-unten-taste, die der spieler drückt.

Dann muss ein neuer Block kommen, die Threads sind komplexer und man kann das nicht so stur machen.

Das is ja eben das Problem. Beide, der automatische move-thread, sowie auch der keyListener, müssen die selben methoden auf der collection (siehe mein code) aufrufen. Die Arbeitsweise des einen hängt von der des anderen ab.

That's the problem 

Und that's nicht zu lösen für mich, und mit deinem Code, wie er jetzt ist, kann man das auch nicht realisieren


----------



## Marco13 (20. Feb 2008)

Mensch  :? ich hätte den Thread, nachdem ich das


> ..leider ist das nicht so simpel wie ihr es hier darstellt..


gelesen hatte, vielleicht mal zuende lesen sollen, bevor ich hier........

```
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;
import java.util.List;

class Tetris extends JFrame implements KeyListener
{
    public static void main(String args[])
    {
        new Tetris();
    }

    private TetrisPanel tetrisPanel = new TetrisPanel();
    private TetrisGame tetrisGame = new TetrisGame(tetrisPanel);

    public Tetris()
    {
        setSize(300,300);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        getContentPane().add(tetrisPanel);
        addKeyListener(this);
        setVisible(true);
        tetrisGame.startGame();
    }

    public void keyPressed(KeyEvent k) {
        switch(k.getKeyCode()){

            case KeyEvent.VK_LEFT:
                tetrisGame.moveLeft();
                break;

            case KeyEvent.VK_RIGHT:
                tetrisGame.moveRight();
                break;

            case KeyEvent.VK_DOWN:
                tetrisGame.moveDown();
                break;

            case KeyEvent.VK_UP:
                break;
        }
    }
    public void keyTyped(KeyEvent k) {}
    public void keyReleased(KeyEvent k) {}

}


class TetrisPanel extends JPanel
{
    private List<Block> blocks = new ArrayList<Block>();

    public void addBlock(Block block)
    {
        blocks.add(block);
    }

    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        for (Block block : blocks) block.draw(g);
    }
}


class Block
{
    private int x = 120;
    private int y = 0;

    public void draw(Graphics g)
    {
        g.fillRect(x,y,10,10);
    }

    public void moveDown()
    {
        y += 10;
    }
    public void moveLeft()
    {
        x -= 10;
    }
    public void moveRight()
    {
        x += 10;
    }

    public boolean isDropped()
    {
        return y >= 250;
    }
}


class TetrisGame implements Runnable
{

    private List<Block> blocks = new ArrayList<Block>();
    private TetrisPanel tetrisPanel;
    private Block currentBlock;

    public TetrisGame(TetrisPanel tetrisPanel)
    {
        this.tetrisPanel = tetrisPanel;
    }

    public void startGame()
    {
        Thread t = new Thread(this);
        currentBlock = new Block();
        tetrisPanel.addBlock(currentBlock);
        t.start();
    }

    public void run()
    {
        while (true)
        {
            if (currentBlock.isDropped())
            {
                currentBlock = new Block();
                tetrisPanel.addBlock(currentBlock);
            }
            try
            {
                Thread.sleep(500);
            } catch (InterruptedException ex) {}

            currentBlock.moveDown();
            tetrisPanel.repaint();
        }
    }


    public void moveLeft()
    {
        currentBlock.moveLeft();
        tetrisPanel.repaint();
    }
    public void moveRight()
    {
        currentBlock.moveRight();
        tetrisPanel.repaint();
    }
    public void moveDown()
    {
        currentBlock.moveDown();
        tetrisPanel.repaint();
    }

}
```
....aber die Lösung von André Uhres ist in mancher Hinsicht sicher schöner....


EDIT: Und die Nachfrae hätte ich vielleicht auch noch abwarten sollen :roll:


----------



## Guest (20. Feb 2008)

oh leute danke ^^

aber wie gesagt.. es geht hier um das konkrete Problem, das ich habe. Eure Beispiele genügen halt nicht den Anforderungen, deswegen bringt mich das nicht weiter leider :/

ps: @marco13 ich kann dein Spiel nicht ausführen? Er sagt noClassDefFound: Game
weiss nicht obs an mir liegt? Aber finde das Problem nicht..


----------



## André Uhres (20. Feb 2008)

Anonymous hat gesagt.:
			
		

> .. es geht hier um das konkrete Problem, das ich habe. Eure Beispiele genügen halt nicht den Anforderungen, deswegen bringt mich das nicht weiter leider ..


Man muss das eben noch Schrittweise erweitern. Das Grundprinzip müsste aber jedenfalls richtig sein.
Ich hab das hier mal ein wenig erweitert:
TetrisDemo.jar (Quellcode im jar)


----------



## Guest (20. Feb 2008)

danke, danke.

aber bevor ihr hier viel zeit investiert gehe ich mal genz genau ins detail, 
denn die implementierung einer kollisionsabrage etc hab ich ja alles schon, der code den ich gepostet
hab ist ja nur 1 von mehreren klassen.


es geht mir, wenn man bis zum ende denkt, eigentlich nur um eine sache, und das ist die taste nach unten, also das dropToBottom in deinem beispiel:

in diesem fall müsste ich nämlich 1:1 das tun, was der else-zweig des move-threads macht (bitte nochmal oben im code nachsehen!) denn es ist das selbe, ich beweg ihn halt bis er kollidiert, und nix anderes passiert im move-thread, nur halt dort nicht auf einen schlag.

das problem dabei ist z.B. das "aufleuchten" der lines. wenn ihr euch bitte den else-zweig des move-threads anseht (ich post ihn jetzt nochmal schnell):


```
// else heisst also: der stein is gedumped:
else {
                        Param.now.setDumped();
                        Param.now = Param.next;
                        c.addElement(Param.now);
                        Param.next = Generator.create();

                        Param.updateGame(0, flashLines(c.checkForLines()));

                        if (c.checkForGameOver()) {
                            Param.running = false;
                            Param.saveHighscore();
                        }
                        ip.update();
                    }
```

es wird in dem falle die methode checkForLines() aufgerufen. diese stellt fest, ob es komplett befüllte zeilen gibt, die ja bekanntlich im Tetris dann verschwinden müssen. flashLines() wird mit dem ergebnis dieser methode aufgerufen.
und die macht dann, falls flashLines "sagt", dass zeilen gelöscht werden müssen:


```
public int flashLines(int[] a) {
        if (a.length > 0) {
            int flashTime = 2;
            while (--flashTime >= 0) {

                for (int i = 0; i < a.length; i++) {
                    c.lightUpLine(i);
                }
                repaint();

                try {
                    Thread.sleep(250);
                } catch (InterruptedException e) {
                }

                for (int i = 0; i < a.length; i++) {
                    c.lightDownLine(i);
                }
                repaint();

                try {
                    Thread.sleep(250);
                } catch (InterruptedException e) {
                }
            }

        }
         // ************ zeilen hier tatsächlich aus der collection löschen und darüberliegende zeilen 
        // ************ nachrutschen lassen:

        for (int i = 0; i < a.length; i++) {
            c.removeLine(a[i]);
            for (int j = 0; j < c.size(); j++) {
                if (c.get(j).getLine() < a[i]) {
                    c.get(j).fall();
                }
            }
        }
        return a.length;
    }
```

das heisst, sie lässt diese zeilen zweimal aufblinken., und löscht sie dann.

zurück zum problem: beim move-thread is das kein thema, da die sleep-anweisungen in der flashLines()-methode den thread anhalten, und er erst wieder in seinen normalen "stein-nach-unten-bewegen-cycle" kommt, wenn das alles passiert ist.

wenn nun aber der spieler die taste nach unten drückt, muss ja das selbe passieren.

prinzipiell könnte der keyListener wirklich nur den stein nach unten bewegen, und im nächsen durchlauf wird der move-thread festellen: e.isDumped() == TRUE und es würde alles nach plan verlaufen.

problem: das ganze soll sofort passieren, was es so aber nicht tut. denn der move-thread könnte z.B. gerade eben erst in seine sleep-methode eingetreten sein, wenn der spieler den stein droppt. dann gibt es ne verzögerung von knapp 500ms bis die lines aufblinken und alles. man kann auch glück haben und der move-thread hat gerade nen neuen cycle begonnen, das gehts sofort.
aber das is ja keine vernünftige lösung.

ich muss also den move-thread aus seinem schlaf reissen, wenn der keyListener nen stein droppt! ich hab inzwischen mitbekommen, dass anscheinend interrupt() dafür sorgen kann. ist zwar sehr seltsam, da in der API steht das unterbricht einen thread (heisst ja auch so) aber okay, das muss ich mal testen. ich muss mir nur irgendwie überlegen wie der keyListener den move-Thread kennt? er braucht ja seinen namen um ein interrupt() darauf aufzurufen oder?

ich hoffe, ihr versteht nun genau, woran ich scheitere. es geht nich ums spielprinzip, das hab ich wie gesagt schon 3 mal gemacht. ich hab auch ein laufendes tetris hier, aber es hat eben die o.g. probleme, und das gefällt mir nicht.

das meinte ich damit, als ich sagte, die threads sind abhängig voneinander! das muss flüssig laufen, egal welcher thread jetz was macht! Ich kann nicht stur den move-thread für 500ms schlagen legen...

any ideas?


----------



## hdi (20. Feb 2008)

*edit*

okay now, damit ihr genau wisst was ich mein, hier mal ne abgespeckte, vereinfachte version vom game:

http://rapidshare.com/files/93417218/newtetris.jar.html

(die klasse mit der main-Methode heisst "Main" falls das Archiv irgendwie beschädigt ist und man es manuell aufrufen muss. Ich hab die Datei auch hier in mein Account hochgeladen, aber wie füge ich sie dann in diesne Text ein? Woher krieg ich den link?)

wenn ihr den stein droppt, werdet ihr merken, dass das flashen der linien (falls ne reihe voll ist) sowie das erstellen eines neuen blocks verzögert ist. DAS ist mein prob.
also ich muss aus dem keyListener, wenn diese pfeiltaste nach unten gedrückt wurde, den move-thread sofort aufwecken 

wer mir sagt, wie ich das mache, hat denk ich das game zum goldstatus gemacht, dann werd ich nur noch bissi rumspielen aber es muss dann alles soweit passen


----------



## André Uhres (20. Feb 2008)

Ich fasse nochmal zusammen.
Du brauchst nur zwei Threads: den Timerthread und den Eventthread.
Im Timerthread wird eine Figur gestartet, im 500ms Takt nach unten bewegt und das Nötige getan wenn sie angekommen ist. 
Alles Andere geschieht im Eventthread. 
Die geringe Reaktionsverzögerung beim manuellen Dropdown könnte man einfach so belassen.
Wer sich auf das Spiel konzentriert, dem fällt das wohl kaum auf.
Andernfalls schafft ein thread.interrupt() Abhilfe.
In meinem Beispiel hab ich den thread.interrupt() einfach mal eingebaut.

Bei den eigenen Dateien siehst du die URL gewöhnlich unten im Browser, wenn die Maus drüber ist.
Mit einem Rechstklick kann man z.B. im Firefox den Link auch kopieren.

Mit deiner jar komm ich übrigens nicht klar. Hab schon am Manifest rumgedoktert, aber ohne Erfolg.


----------



## Marco13 (20. Feb 2008)

Anonymous hat gesagt.:
			
		

> das hab ich wie gesagt schon 3 mal gemacht. ich hab auch ein laufendes tetris hier, aber es hat eben die o.g. probleme, und das gefällt mir nicht.


Ohne dich nun (meht als angebracht) diskreditieren zu wollen: Wenn ich sowas sehe wie "Param.running", muss ich davon ausgehen, dass "Param" eine Klasse (mit einem ... fragwürdigen Namen) ist, und "running" eine "public static (nicht-final) boolean"  :autsch:  Genauso wie Param.now, Param.next usw. Und ob es nun schlimmer ist, eine "Collection" ungetypt zu verwenden, oder ihr so einen grandiosen namen wie "c" zu geben, weiß ich nicht. 

Jedenfalls (und darum gibg's jetzt nur) sind *DAS* alles Gründe für mich, mir deinen Code garnicht erst anzusehen. (Sorry).

Zur letzten Frage: Du solltest wohl nicht mit "interrupt" arbeiten. Stattdessen könntest du da, wo jetzt
Thread.sleep(500);
steht, sowas schreiben wie

```
synchronized (someObject)
{
    someObject.wait(500);
}
```
und ihn an dieser stelle dann "aufwecken", indem du 

```
synchronized (someObject)
{
    someObject.notify();
}
```
aufrufst. (Ja, dann stimmen die 500 nichtmehr, und man muss danach VIELLEICHT(!) noch so lange warten, wie notwendig, damit die 500 voll werden usw.... )


> ..leider ist das nicht so simpel wie ihr es hier darstellt..


... die meisten wissen dass, aber offenbar ist es noch un-simpler, als du es dir vorstellst :wink:


----------



## André Uhres (20. Feb 2008)

Ich hab mein Beispiel jetzt mal so erweitert, dass volle Reihen gelöscht werden.
Und siehe da: thread.interrupt() funzt wie ein geölter Blitz! TetrisDemo.jar (Quellcode im jar)


----------



## hdi (20. Feb 2008)

@ marco13:

"Param" steht für Parameter, bei mir in der uni eine gängige Klassendefinition für bestimmte, feste Werte.
Wie genial das jetzt ist, ist denke ich nicht allzu wichtig.

Dass die Benutzung von tausend globalen statischen Variablen dilletantisch ist, weiss ich. Allerdings ist es für mich der beste weg, ich will nochmal drauf aufmerksam machen, dass der hier gezeigte code lediglich eine von ca. 15 Klassen vom Programm ist (allerdings nimmt auch jeder Tetris-Stein eine eigene Klasse ein, hört sich also schlimmer an als es ist)
U.a. ist da noch ein weiteres Panel auf dem Frame, dieses muss auch über den Status von allem informiert sein, deshalb die globalen Variablen. Ich weiss wie gesagt, dass es nicht gut ist, aber ich wüsste nicht wie sonst.
Mit fehlt einfach die praktische Übung, um zu sehen, was sinnvoll ist, und was nicht.

Wegen der Variablen "c" : Ich finde, jetz übertreibste aber n bisschen 
Die komplette Klasse besitzt_ EINE_ variable, und das ist ne Collection. Deshalb "c" für COllection, könnte auch "col", "coll" oder "collection" schreiben. 
Aber über die 150 Zeilen Code kann ich mir die einzige Variable in der Klasse durchaus merken, und mein Code war vernünftig für euch kommentiert wie ich finde. Ich denke nicht, dass du den Code gelesen hättest, wenn die Variable anders heißen würde 

(was du mit ungetypter Collection meinst, weiss ich nicht. Das is eine eigene Klasse, und zwar nix weiter als eine ArrayList<Block>)

Naja, aber bevor ich hier noch blasphemischer werde: Ihr habt ja recht, aber Anfänger is halt Anfänger, und in Java finde ich ist das ziemlich schwer, vorallem wenn man sich Threading und Swing als erstes Thema auswählt.

@André:
Tatsache dein Game funktioniert sehr schön. Ich versuch das mal auf meinen Code zu adaptieren.
Doof dass du mein Programm nicht starten kannst, dann würdest du genau die Unterschiede sehen.
die "geringe" Verzögerung von der du nämlich sprichst, sind eben bis zu 500ms.

Und ab 60-100 ms lokalem ping nimmt man das spiel "unflüssig" wahr. eine halbe sekunde is natürlich mehr als nur n witz! das merkt man AUF JEDEN FALL ^^

Okay nun, ich versuche mal das ganze mit interrupt()... (ich denk bald ist die schwere Geburt vorbei. Bald, noch nich jetz^^)


----------



## schalentier (21. Feb 2008)

byto hat gesagt.:
			
		

> Auch in Deinem Code musst Du synchronisieren. Swing ist schließlich nicht thread-safe.
> 
> Im übrigen bezweifel ich, dass das Spiel flüssig laufen wird, wenn Eingabe, Berechnung und Painting sequentiell abgearbeitet werden.



Nee, es gibt auch Active Rendering bei Java. Dabei gibts quasi keinen EDT, sondern man muss das Rendering manuell anstossen, bzw jeder Graphics2D-Befehl wird direkt umgesetzt. Dabei nutzt man sinnigerweise einen Backbuffer, in den alles gemalt wird und sobald das Frame fertig ist, wird der Backbuffer in einem Rutsch in das Canvas gezeichnet. 

http://www.gamedev.net/reference/programming/features/javarender/

Das laeuft super fluessig und man muss sich nicht um Thread-Synchronisierung kuemmern. Im uebrigen macht man das schon immer so ^^


----------



## Marco13 (21. Feb 2008)

André Uhres hat gesagt.:
			
		

> Und siehe da: thread.interrupt() funzt wie ein geölter Blitz!


Hm. Ob man "interrupt" wirklich als elementaren Teil der Spiel-Ablauf-Steuerung verwenden sollte...?  ???:L Dass ein interrupt() eine _Ausnahmensituation_ darstellt, sieht man IMHO nicht zuletzt daran, dass dort eine _Exception_ geworfen wird...  Aber ... ich werde mich da jetzt mal nicht weiter reindenken...  :roll:


----------



## Guest (21. Feb 2008)

OK also ich hab das jetzt auch mit interrupt gemacht und es funktioniert wirklich sehr sehr gut!
Ich musste alles nur bisschen verändern weil mein Panel ja kein Thread war (extends JPanel schon) sondern das Runnable Interface implementiert hat. Aber scheinbar kann man das dann so nicht machen,
also interrupt oder this.interrupt checkt er nich.

Aber nun läuft's ja!

*Vielen Dank für eure Bemühungen !*

ps: bald wird das Spiel komplett fertig sein und ich find es macht richtig bock, ich werds dann mal hochladen und ihr könnt "unser" spiel zocken


----------

