# Überlappende Steine



## mvitz (24. Feb 2009)

Hallo zusammen,

da ich immer mal wieder mit dem Gedanken gespielt habe doch endlich mal ein kleines Java Projekt zu starten und hier so häufig Tetris auftaucht, hab ich damit auch mal begonnen.

Als Basis habe ich das Tutorial von Quaxli für 2D Spieleprogrammierung genommen. Soweit funktioniert auch schon einiges nur folgendes passt bisher nicht (siehe Anhang).

Wenn Steine aufeinander fallen, kann es dazu kommen, dass diese überlappen. Ich vermute das liegt daran, da meine Steine von der Klasse java.awt.geom.Rectangle2D.Double erbt und diese nunmal das Koordinatensystem mit double verwaltet. Gibt es eine Möglichkeit ein Integer Koordinatensystem zu bekommen?


----------



## hdi (24. Feb 2009)

Wofür nutzt du denn double? Steig doch gleich auf int um, also nimm normale Rectangles.
Halbe pixel gibt es eh nicht.


----------



## mvitz (24. Feb 2009)

Also habe jetzt direkt von java.awt.Rectangle geerbt. Der Fehler ist jedoch immer noch vorhanden. Ich denke fast, er hängt mit meiner Kollisionsabfrage (lediglich per x.intersects(s)) zusammen. Werde mir das gleich nachm essen nochmal anschauen.


----------



## schalentier (24. Feb 2009)

Post ma den Code fuer die Kollisionsabfrage... sonst wirds schwierig mit der Hilfe


----------



## mvitz (24. Feb 2009)

[HIGHLIGHT="Java"]
public class GamePanel extends JPanel implements Runnable, KeyListener {

	private static final long serialVersionUID = 1L;
	private boolean gameRunning = true;
	private List<Sprite> oldBlocks;
	private Sprite actualBlock;
	private final int startHeight = 2 * Sprite.SQUARE;
	private final int startWidth = 6 * Sprite.SQUARE;

	public GamePanel(int theWidth, int theHeight) {
		Dimension dimension = new Dimension(theWidth, theHeight);
		setPreferredSize(dimension);
		setMaximumSize(dimension);
		setMinimumSize(dimension);
		JFrame frame = new JFrame("Tetris");
		frame.setLocation(100, 100);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setResizable(false);
		frame.addKeyListener(this);
		frame.add(this);
		frame.pack();
		frame.setVisible(true);
		doInit();
	}

	private void doInit() {
		last = System.nanoTime();
		oldBlocks = new LinkedList<Sprite>();
		actualBlock = getRandomBlock();
		Thread thread = new Thread(this);
		thread.start();
	}

	@Override
	public void run() {
		while (gameRunning) {
			computeDelta();
			doLogic();
			moveObjects();
			repaint();
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO: handle exception
			}
		}
	}

	private void moveObjects() {
		actualBlock.move(delta);
		if (!actualBlock.canMove()) {
			oldBlocks.add(actualBlock);
			System.out.println(actualBlock.getY());
			if (actualBlock.getY() < startHeight + Sprite.SQUARE) {
				gameRunning = false;
			} else {
				actualBlock = getRandomBlock();
			}

		}
	}

	private void doLogic() {
		actualBlock.doLogic(delta);
		for (Sprite sprite : oldBlocks) {
			if (actualBlock.intersects(sprite)) {
				actualBlock.setMoved(false);
			}
		}
	}

	@Override
	protected void paintComponent(Graphics g) {
		super.paintComponent(g);
		g.setColor(Color.RED);
		g.drawString("FPS: " + Long.toString(fps), 20, 10);
		actualBlock.drawObjects(g);
		for (Drawable drawable : oldBlocks) {
			drawable.drawObjects(g);
		}
	}
}[/HIGHLIGHT]


----------



## hdi (24. Feb 2009)

Also nur geraten weil man sieht ja nicht den ganzen Code:


```
actualBlock.move(delta);
if (!actualBlock.canMove())
```

Erstens mal denk ich, solltest du erst abfragen, und dann bewegen.
Sonst isses ja schon zu spät?

Aber vllt rechnest du auch vor. Aber mmN braucht die Methode canMove()
doch noch den Parameter delta? Wie willst du sonst bestimmen, ob er sich bewegen 
darf, wenn du nich mal weisst um wieviel er sich bewegen soll.

Aber vllt versteh ich auch den Code nicht.
Ich kapier auch nicht was startHeight und startWidth sein soll.
Hast du das vllt verdreht? Dein Spielfeld liegt doch nicht horizontal oder.

Ich denke es wär noch hilfreich wenn du mal die canMove() Methode zeigst,
sowie die move() Methode.


----------



## mvitz (24. Feb 2009)

Also:

Das geht bei mir so, da:
[HIGHLIGHT="Java"]	@Override
	public void doLogic(long delta) {
		if (parent.getHeight() - height<= y) {
			speed = 0;
			moves = false;
		}
	}

	@Override
	public void move(long delta) {
		y += speed;
	}[/HIGHLIGHT]
und doLogic vor move aufgerufen wird. (Habs jetzt aber trotzdem geändert:
[HIGHLIGHT="Java"]	private void moveObjects() {
		if (actualBlock.canMove()) {
			actualBlock.move(delta);
		} else {
			oldBlocks.add(actualBlock);
			if (actualBlock.getY() < startHeight + Sprite.SQUARE) {
				gameRunning = false;
			} else {
				actualBlock = getRandomBlock();
			}
		}
	}[/HIGHLIGHT]
Die innere if-Abfrage dient dazu, ob die Steine oben überquellen und das Spiel somit zu Ende ist.

Die delta übergaben habe ich bisher noch garnicht benutzt (sind quasi überbleibsel aus dem Tutorial)

startHeight und startWidth ist der Punkt wo ein neuer Block oben erscheint. startHeight ist praktisch nur ein Abstand (zu der FPS anzeige) und startWidth lässt den Block halt mittig und nicht am linken Rand starten.
Ich denke mal es wird an der Kollisionsabfrage liegen.


----------



## Quaxli (24. Feb 2009)

Ich hab' auch mal ein Tetris angefangen, aber nie zu Ende gemacht. Die Basics zum GameLoop hatte ich so wie im Tutorial. Aber bei Tetris gibt es ja Probleme, die Steine zu drehen, zu plazieren und teilweise zu löschen. 
Ich hatte das damals wie folgt gelöst: 
Ich hatte mir eine Klasse namens Grid geschrieben, die das ganze Spielfeld in ein Gitter bzw. ein Rechteck-Array unterteilt hat. 
Meine Steine waren ebenfalls Arrays: 5x5-Rectangle (dabei natürlich etliche die null waren). Das Ganze sah dann etwa so aus:

x x x x x
x o o x x
x x o x x
x x o x x
x x o x x

Damit hat sich auch die Drehung unkompliziert lösen lassen und es war einfach zu berechnen, wo die Einzelteile nach der Drehung angkommen.

x x x x x
x x x o x
o o o o x
x x x x x
x x x x x

Wenn der Stein unten angekommen ist, habe ich die Rechtecke aus dem Stein-Array in das Spiel-Gitter übernommen.
Über dieses Gitter habe ich dann auch die Kollisionsprüfung vorgenommen und ermittelt, wo der nächste Stein plaziert werden muß. Außerdem ist es dann einfacher zu prüfen, ob eine Reihe komplett gefüllt ist und es gibt ebenfalls weniger Probleme wenn man eine Reihe mittendrin löschen muß.


----------



## Marco13 (24. Feb 2009)

Das wäre eher "Pentris"  Aber die eigentlich spannende Frage ist ja: Wenn man die Untere Situation hat, und der Block (also nicht das Grid, sondern der eingezeichnete Block) EINE Reihe über dem Boden ist (wenn das Grid also schon eine Zeile aus dem Spielfeld hinausragt) und man dreht dann (so dass die obere Situation entsteht) - wie geht das dann? Geht das überhaupt? Was passiert dann?


----------



## mvitz (24. Feb 2009)

Evtl. sollte ich dann doch erstmal etwas einfacheres machen 

Aber das mit dem verschieben ist doch irgendwie komisch. Evtl. hat da ja doch noch jemand einen Idee. Folgende Demo zeigt das Problem in größer:
[HIGHLIGHT="Java"]import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.util.LinkedList;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class Test extends JPanel implements Runnable {

	private static final long serialVersionUID = 1L;

	public static void main(String[] args) {
		new Test();
	}

	public static final int SQUARE = 256;
	private List<Square> oldSquares;
	private Color[] colors;
	private Square square;
	private int colorIterator;
	private boolean gameRunning;

	public Test() {
		setPreferredSize(new Dimension(SQUARE, 5 * SQUARE / 2));
		JFrame frame = new JFrame("Tests");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setResizable(false);
		frame.add(this);
		frame.pack();
		frame.setVisible(true);
		doInit();
	}

	private void doInit() {
		gameRunning = true;
		colorIterator = 0;
		colors = new Color[] { Color.CYAN, Color.YELLOW };
		oldSquares = new LinkedList<Square>();
		square = new Square(colors[colorIterator++], this);
		Thread thread = new Thread(this);
		thread.start();
	}

	@Override
	public void run() {
		while (gameRunning) {
			moveObject();
			repaint();
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO: handle exception
			}
		}
	}

	private void moveObject() {
		boolean aCollision = false;
		for (Square aSquare : oldSquares) {
			if (square.intersects(aSquare)) {
				aCollision = true;
				break;
			}
		}
		if (square.canMove() && !aCollision) {
			square.move();
		} else {
			oldSquares.add(square);
			if (square.y <= 0) {
				gameRunning = false;
			} else {
				square = new Square(colors[colorIterator++], this);
				if (colorIterator >= colors.length) {
					colorIterator = 0;
				}
			}
		}

	}

	@Override
	protected void paintComponent(Graphics g) {
		super.paintComponent(g);
		for (int i = -1, j = 1; i < getHeight(); i += (SQUARE / 2), j++) {
			if (j % 2 == 0) {
				g.setColor(Color.BLACK);
			} else {
				g.setColor(Color.WHITE);
			}
			g.fillRect(SQUARE / 2, i, SQUARE, SQUARE / 2);
		}
		for (Square aSquare : oldSquares) {
			aSquare.draw(g);
		}
		square.draw(g);
	}

	class Square extends Rectangle {
		private static final long serialVersionUID = 1L;
		private Color color;
		private int gravity;
		private boolean canMove;
		private int size;
		private Test parent;

		public Square(Color theColor, Test theParent) {
			super();
			color = theColor;
			gravity = 1;
			canMove = true;
			size = SQUARE / 2;
			parent = theParent;
			x = 0;
			y = 0;
			width = size;
			height = size;
		}

		public void draw(Graphics theGraphics) {
			theGraphics.setColor(color);
			theGraphics.fillRect(x, y, size, size);
			theGraphics.setColor(Color.BLACK);
			theGraphics.drawRect(x, y, size, size);
		}

		public void move() {
			if (y + size + 2 * gravity >= parent.getHeight()) {
				stop();
			}
			y += gravity;
		}

		public void stop() {
			canMove = false;
		}

		public boolean canMove() {
			return canMove;
		}
	}
}[/HIGHLIGHT]


----------



## Quaxli (25. Feb 2009)

Marco13 hat gesagt.:


> Das wäre eher "Pentris"  Aber die eigentlich spannende Frage ist ja: Wenn man die Untere Situation hat, und der Block (also nicht das Grid, sondern der eingezeichnete Block) EINE Reihe über dem Boden ist (wenn das Grid also schon eine Zeile aus dem Spielfeld hinausragt) und man dreht dann (so dass die obere Situation entsteht) - wie geht das dann? Geht das überhaupt? Was passiert dann?


Dann formatiert es Deine Festplatten, tötet Deinen Hund und sprengt den Planeten 

Mal im Ernst: Daß hatte ich seinerzeit abgefangen und dann eben nicht gedreht. Sobald bei der Zielkonstellation ein Teilstein außerhalb des Spielfeldes ist, wurde nicht mehr gedreht. Ich hatte das damals etwas umständlich gelöst. Mit der Lösung, die Du vor kurzem im Anfängerforum zu einem Schachbrett vorgeschlagen hast - nämlich das  Gitter etwas größer zu definieren - würde es vermutlich etwa eleganter gehen.


----------



## Quaxli (25. Feb 2009)

@habi55:

Nachdem eine Kollision ja erst zustande kommen kann, wenn die Steine sich überlappen, mußt Deinen Stein dann halt noch zurücksetzen:

[highlight=Java]    private void moveObject() {
        boolean aCollision = false;
        for (Square aSquare : oldSquares) {
            if (square.intersects(aSquare)) {
                  square.y = aSquare.y - square.height; //<<< Position anpaasen
                aCollision = true;
                break;
            }
        }[/highlight]

Für Dein Demo war diese Modifikation ausreichend (in meinen Augen)


----------



## mvitz (25. Feb 2009)

Danke, das wars 

Habe mich zwar jetzt TicTacToe zugewandt (ist für den Anfang dann doch einfacher). Und habe das BasisSpiel auch schon fertig (auch wenn mein Gewinnüberprüfungsalgo wohl atm ziemlich beschiessen ist).

(Gibts hier keinen "Thema gelöst" button?)


----------



## Quaxli (25. Feb 2009)

Hehe, ja lieber klein anfangen. Tetris ist schon ein Stück anspruchsvoller


----------

