Tetris - mögliche bugs und Kritiken

Status
Nicht offen für weitere Antworten.

Das Brot

Mitglied
Hi, ich hab über die Sommerferien ein Tetris-Spiel programmiert. Es lehnt sich größtenteils an das Original Tetris des GameBoys an. Der Highscore wird identisch wie im Original berechnet und auch die Steine und das Spielfeld sind identisch (zumindest von der Form und Größe).


Steuerung:

Pfeiltasten nach oben: Stein drehen
Pfeiltasten nach links: Stein nach links
Pfeiltasten nach rechts: Stein nach rechts
Pfeiltasten nach unten: Stein nach unten

Auf Enter wird der Stein sofort abgelegt und auf P kommt die Pause.


Wollt euch fragen ob ihr es vielleicht mal kurz anspielt und mir irgendwelche Anregungen bezüglich des Umgang mit dem Spiel geben könnt. Vielleicht findet ihr auch noch ein paar Bugs oder so. Wollt einfach mal euere Meinung bezüglich der Strukturrierung des Projektes wissen. Ihr müsst euch natürlich nicht den ganzen Quellcode reinziehen aber ich hab größtenteils alles genügend durchkommentiert.

Was mir persönlich noch nicht gefällt ist natürlich die Oberfäche. Sie ist noch ziemlich einfach gestrickt mit JPanels und co. Aber ich hab mich auch ned lange damit aufgehalten da mich Oberflächenprogrammierung irgendwie weniger interessiert. Methoden und Attribute sind noch auf deutsch das muss ich noch ändern.

Ich hab das ganze Programm in 3 Java-Dateien gepackt. Klassisch nach der 3-Schichten-Architektur in Oberfläche, Steuerung und Datenhaltung.

Habt ihr irgendwelche Verbesserungsvorschläge für mich?

Ich hab das ganze jetzt mal sowohl als .jar Datei , als auch als .class und .java Dateien hier her gepackt.

hf

mfg Brot
 

darkeye2

Bekanntes Mitglied
ich finds vom spiel her zimlich gut, als normaler nicht tetris spieler bin ich etwa 30 min dran gesessen^^ also gute leistung, der code interessiert mich, werde den mal später anschauen, besonders gespannt bin ich darauf, wie du das mit dem drehen der figuren gemacht hast.
 

Marco13

Top Contributor
Jo, passt schon - das Spiel funktioniert, keine Fehler bemerkt, Code kurz überflogen und nichts gesehen was verständnisloses Kopfschütteln ausgelöst hätte.... :toll:
 

Painii

Bekanntes Mitglied
Hab mir jetzt nur den Sourcecode angeschaut... Verstehen ja, aber teilweise etwas kompliziert/unschön (in Oberfläche.java / setPixel():
Java:
if(p_status){
			if(p_Farbe.equals("schwarz"))
				feld[p_y][p_x].setBackground(Color.BLACK);
			if(p_Farbe.equals("gelb"))
				feld[p_y][p_x].setBackground(Color.YELLOW);
			if(p_Farbe.equals("rot"))
				feld[p_y][p_x].setBackground(Color.RED);
			if(p_Farbe.equals("blau"))
				feld[p_y][p_x].setBackground(Color.BLUE);
			if(p_Farbe.equals("grün"))
				feld[p_y][p_x].setBackground(Color.GREEN);
			if(p_Farbe.equals("orange"))
				feld[p_y][p_x].setBackground(Color.ORANGE);
			if(p_Farbe.equals("pink"))
				feld[p_y][p_x].setBackground(Color.PINK);
		}
		else{
			if(p_Farbe.equals("weis"))
				feld[p_y][p_x].setBackground(Color.WHITE);			
		}
Wenn die p_Farbe gleiche eine Color wäre könntest du daraus eine Zeile machen mit:
Java:
feld[p_y][p_x].setBackground(p_Farbe);
Und allgemein find ich is da ne Menge schon statisch festgelegt (die Feldgröße, die Farbe der Steine etc.), das könnte man vielleicht noch alles in den Konstruktor packen, damit man auch ein größeres Feld nehmen könnte.

Rein spielerisch funktioniert es finde ich gut, hab keine Fehler auf die Schnelle entdeckt.

Anregungen:
-Highscore speichern
-Steinfarbe ändern - zwei Steine (die Linie und eins von den "N") haben beide eine gelbliche Farbe, das hat mich etwas irritiert (ich hab mit dem Augenwinkel nurnoch auf die Farbe geachtet), eine davon könnte vielleicht noch geändert werden
-Entertaste zum Steine fallen lassen ändern. Für mich wars ungünstig, weil die Hand auf den Pfeiltasten lag und ich es unbequem find beide Hände auf dem Teil der Tastatur zu haben. Ich würd lieber die Leertaste dafür nehmen (die ist ja auch schön groß :D)

Nach aussen hin siehts auf jeden Fall besser aus als das was ich letztens gebaut habe, Glückwunsch :p
 

Das Brot

Mitglied
Hi danke für die Antworten.

Ja stimmt das mit dem Zeichnen auf das Spielfeld ist ziemlich umständlich. Das kommt noch von früheren Versionen des Spiels als die Steine noch keine Farben hatten und alles Schwarz/Weis war. sollte ich bei Zeit ändern. Dass man mit der Leertaste die Steine nicht absetzen kann war eig ein Hauptgrund es selber zu programmieren xD. Hab öfters im Internet Tetris gespielt und mich immer geärgert, dass es "neuartige" Tetrisversionen waren und die Steuerung so kacke war. Habs erstmal so geschrieben wie es für mich am einfachsten war. Könnte aber noch einbauen dass man sich die Steueurung selber aussuchen kann oder dass man auf Enter UND Leertaste absetzen kann.

Das Drehen der Steine hat etwas Zeit gedauert:

Jeder Stein besteht aus 4 Klötzchen.
Wird nun der Befehel zum drehen gegeben "s.drehen" dann passiert beim "LStein" z.b. das:
Java:
	public void drehen(){
		switch(posModus){
			case 0:
				xPos[1] = xPos[1] + 1;
				xPos[3] = xPos[3] - 1;

				yPos[0] = yPos[0] - 2;
				yPos[1] = yPos[1] - 1;
				yPos[3] = yPos[3] + 1;

				posModus = 1;
			break;

			case 1:
				xPos[0] = xPos[0] + 2;
				xPos[1] = xPos[1] + 1;
				xPos[3] = xPos[3] - 1;

				yPos[1] = yPos[1] + 1;
				yPos[3] = yPos[3] - 1;

				posModus = 2;
			break;

			case 2:	
				xPos[1] = xPos[1] - 1;
				xPos[3] = xPos[3] + 1;

				yPos[0] = yPos[0] + 2;
				yPos[1] = yPos[1] + 1;
				yPos[3] = yPos[3] - 1;

				posModus = 3;
			break;

			case 3:
				xPos[0] = xPos[0] - 2;
				xPos[1] = xPos[1] - 1;
				xPos[3] = xPos[3] + 1;

				yPos[1] = yPos[1] - 1;
				yPos[3] = yPos[3] + 1;

				posModus = 0;
			break;
		}
posModus speichert auf welcher DrehPosition sich der Stein gerade befindet. Danach richtet sich der Algorithmus dann und verschiebn die x und yPositionen der 4 Klötzchen.


Was mir selber viel Kopfzerbrechen beschaft und man auch zweifelsfrei am Quellcode erkennen kann dass es nicht einwandfrei gelöst ist, ist das Blinken der Reihen die vollständig sind, wenn der Stein abgesetzt wird. Durch den ganzen Steuerungsquellcode sind sachen wie z.b. "if(!blinken){}" zu lesen.

Da er mir sonst nach dem Absetzen des Steins zu viele Sachen zu früh ausführt. Das liegt daran, dass ich nach einer vollständigen Linie den Thread für das herunterschieben des Steines auschalte ihn mit einer neuen Geschwindigkeit wieder starte und dann auf den "Blinkvorgang " umleite. Das is scheißkompliziert und hat mich ne Menge Zeit gekostet. Vielleicht fällt ja jemandem ein wie man es besser machen könnte^^


mfg Brot
 

Marco13

Top Contributor
Die "drehen"-Sachen hab' ich mir auch mal angesehen gehabt, und das wirkt(e) im ersten Moment so... redundant und un-elegant, aber ich hatte schonmal (GANZ kurz) drüber nachgedacht, wie man das machen könnte, und glaube: Es ist nicht soooo einfach (und die Zustandsübergänge so wie jetzt einfach direkt hinzuschreiben ist wohl erstmal(!) das einfachste)
 

hdi

Top Contributor
Ohne Garantie auf Richtigkeit, aber das drehen kann man - so ergoogelte ich es mir gerade - per Rotationsmatrix lösen:

Java:
newX = p.x * cos(winkel) - p.y * sin(winkel);
newY = p.x * sin(winkel) + p.y * cos(winkel);
 

Marco13

Top Contributor
:D Das stimmt an sich, aber das ganze muss in einem 4x4-Array passieren, und eine der vielen spannenden Fragen dabei ist, um welchen Punkt denn genau rotiert wird....

EDIT: Abgesehen davon geht es ja nur um Rotationen um +90° oder -90° (oder für Andrey: +PI/2 oder -PI/2), und da kommen bei dem ganzen cos/sin-Zeug nur +1, 0 und -1 raus...
 
Zuletzt bearbeitet:

Das Brot

Mitglied
Also ich hab einen Stein in 2 Arrays gepackt. In yPos[4] werden die 4 Y-Positionen gespeichert und in xPos[4] werden die 4 X-Positionen gespeichtert.

Um nun zum Beispiel die Position vom dritten Klötzchen braucht man xPos[2] und yPos[2].

Beim drehen werden aber die beiden Einträge nicht immer gleich verändert. Es kann sein, dass das Klötzchen um 2 nach unten aber nur um 1 nach rechts verschoben wird, oder es sich nur in eine Richtung bewegt.

Was mir mehr Kopfzerbrechen bereitet ist meine Oberfläche, da ich nicht weis wie ich auf ein JFrame eine professionelle Spieloberfläche bekomme die nicht nach "Ich probier mal aus ob ich Tetris programmieren kann" aussieht :p

Weis da jemand Rat?

mfg Brot
 

faetzminator

Gesperrter Benutzer
Warum machst du für die Steine nicht einzelne Klassen, welche von einer abstrakten Klasse erben und Methoden wie void rotateLeft(), void rotateRight() und List<Point> getPoints() zur Verfügung stellen? Dann kannst du intern mit einer Liste von vier Point Objekten arbeiten und das Ganze ist schön objektorientiert.
 

Marco13

Top Contributor
@faetzminator: Hast du's schonmal versucht? Also, ich hatte damals mal kurz ernsthaft drüber nachgedacht, und das "schön" und "elegant" zu machen ist (bei allen Programmen die große Herausforderung, aber speziell bei diesem Problem) nicht so einfach, wie ich zuerst dachte.... Was aber nicht heißen soll, dass man nicht (wenn man ein bißchen länger drüber nachdenkt) eine solche Lösung findet - ich meine nur, dass ich sie damals nicht einfach so straightforward aus dem Ärmel schütteln konnte.
 

Das Brot

Mitglied
Hab ich^^ Schau dir mal Feld.java an.

Da gibt es eine Abstrakte Oberklasse von der alle Steinklassen erben

Gut könnte Point Objekte nehmen was aber nichts daran ändert dass ich pro Klötzchen 2 Befehle (einmal für X-Bewegung und einmal für Y-Bewegung ) ausführen muss.

rotateLeft und rotateRight erübrigen sich, da die Steine sich immer nur im Uhrzeigersinn drehen.

getYPos(int i) und getXPos(int i) Besitzen diese Klassen auch.

Hab ich also genauso gemacht, bis auf die PointObjekte, die könnten das ganze noch vereinfachen, da hast du recht.

Hier mal die Abstracte Oberklasse und Unterklassen:
Java:
// Speichert Position der fallenden Steine
abstract class Stein{

	/*
	 * posModus gibt an welche Position der Stein Momentan hat:
	 * Klotz hat nur eine Position,
	 * Stange und ParallelL & R  haben 2 Positionen
	 * LSteinL & R haben 4 Positionen
	 *
	 *	= 0 -> waagrecht bei Stange, ParallelL & R , LSteinL & R
	 *	= 1 -> senkrecht bei Stange, ParallelL & R , LSteinL & R
	 *  = 2 -> waagrecht2 bei LSteinL & R
	 *  = 3 -> senkrecht2 bei LSteinL & R
	 *
	 * PosModus hilft beim Drehen der Steine
	 */

	protected int posModus = 0;

	protected int xPos[] = new int[4];
	protected int yPos[] = new int[4];

	public void setXPos(int p_xPos, int p_nr){
		xPos[p_nr] = p_xPos;
	}
	public void setYPos(int p_yPos, int p_nr){
		yPos[p_nr] = p_yPos;
	}

	public int getXPos(int p_nr){
		return xPos[p_nr];
	}
	public int getYPos(int p_nr){
		return yPos[p_nr];
	}

	// Verschiebt den Stein nach L, R, U oder O
	public void verschiebenO(){	
		yPos[0] --;
		yPos[1] --;
		yPos[2] --;
		yPos[3] --;		
	}
	public void verschiebenU(){	
		yPos[0] ++;
		yPos[1] ++;
		yPos[2] ++;
		yPos[3] ++;		
	}
	public void verschiebenR(){	
		xPos[0] ++;
		xPos[1] ++;
		xPos[2] ++;
		xPos[3] ++;
	}
	public void verschiebenL(){	
		xPos[0] --;
		xPos[1] --;
		xPos[2] --;
		xPos[3] --;
	}

	// Wird von den Subklassen überschrieben
	public void drehen(){}

	// Wird von den Subklassen überschrieben 
	public String gibFarbe(){
		return "schwarz";
	}
	public int gibSteinNummer(){
		return -1;
	}
}

// Unterschiedliche Steinsorten im Konstruktor steht Struktureigenschaften der Steine
class Stange extends Stein{
	public Stange(){
		xPos[0] = 3;
		xPos[1] = 4;
		xPos[2] = 5;
		xPos[3] = 6;
	
		yPos[0] = 1;
		yPos[1] = 1;
		yPos[2] = 1;
		yPos[3] = 1;
	}

	public void drehen(){
		switch(posModus){
			case 0:	
				xPos[0] = xPos[0] + 1;
				xPos[2] = xPos[2] - 1;
				xPos[3] = xPos[3] - 2;

				yPos[0] = yPos[0] + 1;
				yPos[2] = yPos[2] - 1;
				yPos[3] = yPos[3] - 2;

				posModus = 1;
			break;

			case 1: 
				xPos[0] = xPos[0] - 1;
				xPos[2] = xPos[2] + 1;
				xPos[3] = xPos[3] + 2;

				yPos[0] = yPos[0] - 1;
				yPos[2] = yPos[2] + 1;
				yPos[3] = yPos[3] + 2;			

				posModus = 0;			
			break;
		}
	}

	public String gibFarbe(){
		return "orange";
	}

	public int gibSteinNummer(){
		return 0;
	}
}

class Klotz extends Stein{
	public Klotz(){
		xPos[0] = 4;
		xPos[1] = 5;
		xPos[2] = 4;
		xPos[3] = 5;
	
		yPos[0] = 1;
		yPos[1] = 1;
		yPos[2] = 2;
		yPos[3] = 2;
	}

	public String gibFarbe(){
		return "schwarz";
	}

	public int gibSteinNummer(){
		return 1;
	}
}

class ParallelL extends Stein{
	public ParallelL(){
		xPos[0] = 3;
		xPos[1] = 4;
		xPos[2] = 4;
		xPos[3] = 5;

		yPos[0] = 1;
		yPos[1] = 1;
		yPos[2] = 2;
		yPos[3] = 2;
	}

	public void drehen(){
		switch(posModus){
			case 0:
				xPos[0] = xPos[0] + 1;
				xPos[2] = xPos[2] - 1;
				xPos[3] = xPos[3] - 2;

				yPos[0] = yPos[0] - 1;
				yPos[2] = yPos[2] - 1;

				posModus = 1;
			break;

			case 1:
				xPos[0] = xPos[0] - 1;
				xPos[2] = xPos[2] + 1;
				xPos[3] = xPos[3] + 2;

				yPos[0] = yPos[0] + 1;
				yPos[2] = yPos[2] + 1;

				posModus = 0;
			break;
		}
	}

	public String gibFarbe(){
		return "gelb";
	}

	public int gibSteinNummer(){
		return 2;
	}
}

class ParallelR extends Stein{
	public ParallelR(){
		xPos[0] = 3;
		xPos[1] = 4;
		xPos[2] = 4;
		xPos[3] = 5;

		yPos[0] = 2;
		yPos[1] = 2;
		yPos[2] = 1;
		yPos[3] = 1;
	}

	public void drehen(){
		switch(posModus){
			case 0:
				xPos[0] = xPos[0] + 2;
				xPos[1] = xPos[1] + 1;
				xPos[3] = xPos[3] - 1;

				yPos[1] = yPos[1] - 1;
				yPos[3] = yPos[3] - 1;

				posModus = 1;
			break;

			case 1:
				xPos[0] = xPos[0] - 2;
				xPos[1] = xPos[1] - 1;
				xPos[3] = xPos[3] + 1;

				yPos[1] = yPos[1] + 1;
				yPos[3] = yPos[3] + 1;

				posModus = 0;			
			break;
		}
	}

	public String gibFarbe(){
		return "rot";
	}

	public int gibSteinNummer(){
		return 3;
	}
}

class LSteinL extends Stein{
	public LSteinL(){
		xPos[0] = 3;
		xPos[1] = 3;
		xPos[2] = 4;
		xPos[3] = 5;
	
		yPos[0] = 2;
		yPos[1] = 1;
		yPos[2] = 1;
		yPos[3] = 1;
	}

	public void drehen(){
		switch(posModus){
			case 0:
				xPos[1] = xPos[1] + 1;
				xPos[3] = xPos[3] - 1;

				yPos[0] = yPos[0] - 2;
				yPos[1] = yPos[1] - 1;
				yPos[3] = yPos[3] + 1;

				posModus = 1;
			break;

			case 1:
				xPos[0] = xPos[0] + 2;
				xPos[1] = xPos[1] + 1;
				xPos[3] = xPos[3] - 1;

				yPos[1] = yPos[1] + 1;
				yPos[3] = yPos[3] - 1;

				posModus = 2;
			break;

			case 2:	
				xPos[1] = xPos[1] - 1;
				xPos[3] = xPos[3] + 1;

				yPos[0] = yPos[0] + 2;
				yPos[1] = yPos[1] + 1;
				yPos[3] = yPos[3] - 1;

				posModus = 3;
			break;

			case 3:
				xPos[0] = xPos[0] - 2;
				xPos[1] = xPos[1] - 1;
				xPos[3] = xPos[3] + 1;

				yPos[1] = yPos[1] - 1;
				yPos[3] = yPos[3] + 1;

				posModus = 0;
			break;
		}
	}

	public String gibFarbe(){
		return "grün";
	}

	public int gibSteinNummer(){
		return 4;
	}
}

class LSteinR extends Stein{
	public LSteinR(){
		xPos[0] = 3;
		xPos[1] = 4;
		xPos[2] = 5;
		xPos[3] = 5;
	
		yPos[0] = 1;
		yPos[1] = 1;
		yPos[2] = 1;
		yPos[3] = 2;
	}

	public void drehen(){
		switch(posModus){
			case 0:	
				xPos[0] = xPos[0] + 1;
				xPos[2] = xPos[2] - 1;
				xPos[3] = xPos[3] - 2;

				yPos[0] = yPos[0] - 1;
				yPos[2] = yPos[2] + 1;

				posModus = 1;
			break;

			case 1: 
				xPos[0] = xPos[0] + 1;
				xPos[2] = xPos[2] - 1;

				yPos[0] = yPos[0] + 1;
				yPos[2] = yPos[2] - 1;
				yPos[3] = yPos[3] - 2;

				posModus = 2;
			break;

			case 2:	
				xPos[0] = xPos[0] - 1;
				xPos[2] = xPos[2] + 1;
				xPos[3] = xPos[3] + 2;

				yPos[0] = yPos[0] + 1;
				yPos[2] = yPos[2] - 1;

				posModus = 3;
			break;

			case 3:	
				xPos[0] = xPos[0] - 1;
				xPos[2] = xPos[2] + 1;
	
				yPos[0] = yPos[0] - 1;
				yPos[2] = yPos[2] + 1;
				yPos[3] = yPos[3] + 2;

				posModus = 0;
			break;
		}
	}

	public String gibFarbe(){
		return "blau";
	}

	public int gibSteinNummer(){
		return 5;
	}
}

class Dreieck extends Stein{
	public Dreieck(){
		xPos[0] = 3;
		xPos[1] = 4;
		xPos[2] = 4;
		xPos[3] = 5;
	
		yPos[0] = 1;
		yPos[1] = 1;
		yPos[2] = 2;
		yPos[3] = 1;
	}

	public void drehen(){
		switch(posModus){
			case 0:
				xPos[0] = xPos[0] + 1;
				xPos[2] = xPos[2] - 1;
				xPos[3] = xPos[3] - 1;

				yPos[0] = yPos[0] - 1;
				yPos[2] = yPos[2] - 1;
				yPos[3] = yPos[3] + 1;

				posModus = 1;
			break;

			case 1:
				xPos[0] = xPos[0] + 1;
				xPos[2] = xPos[2] + 1;
				xPos[3] = xPos[3] - 1;

				yPos[0] = yPos[0] + 1;
				yPos[2] = yPos[2] - 1;
				yPos[3] = yPos[3] - 1;

				posModus = 2;
			break;

			case 2:	
				xPos[0] = xPos[0] - 1;
				xPos[2] = xPos[2] + 1;
				xPos[3] = xPos[3] + 1;

				yPos[0] = yPos[0] + 1;
				yPos[2] = yPos[2] + 1;
				yPos[3] = yPos[3] - 1;

				posModus = 3;
			break;

			case 3:
				xPos[0] = xPos[0] - 1;
				xPos[2] = xPos[2] - 1;
				xPos[3] = xPos[3] + 1;

				yPos[0] = yPos[0] - 1;
				yPos[2] = yPos[2] + 1;
				yPos[3] = yPos[3] + 1;

				posModus = 0;
			break;
		}
	}

	public String gibFarbe(){
		return "pink";
	}

	public int gibSteinNummer(){
		return 6;
	}
}

mfg Brot
 

andre111

Bekanntes Mitglied
Wenn
Java:
public void drehen(){}
von den Unterklassen implementiert werden soll, dann würde ich es als abstract deklarieren:
Java:
public abstract void drehen(){}
 

Das Brot

Mitglied
Hier ist n Screenshot von meinem Klassen package.

klassenscreenshot.jpg



Ich wollte mal fragen warum manche Klassen doppelt oder gar dreifach sind und ein '$'-Zeichen hinten dran haben.

Wenn ich die lösche kommen sie beim nächsten compilieren wieder. Was für einen Sinn haben diese Klassen?

mfg Brot
 

Marco13

Top Contributor
Das sind anonyme Klassen
Java:
class SomeClass
{
    void method()
    {
        // Für diesen ActionListener wird eine Datei wie "SomeClass$1.class" erstellt:
        button.addActionListener(new ActionListener()
        {
            void actionPerformed(...) {... }
        });
    }
}
 
Status
Nicht offen für weitere Antworten.

Ähnliche Java Themen

Neue Themen


Oben