GUI Tic Tac Toe

J

Jolies

Gast
Hallo!
Ich soll Tic Tac Toe in Java programmieren.. soweit bin ich recht zufrieden mit meinem kleinen Programm, aber die Schrift, wer gerade drann ist (Zeile 28ff), überschreibt sich immer wieder, so das nichts mehr lesbar ist.. Hat jemand einen Tipp für mich, wie sich das ändern liese?
Und ich komm einfach nicht drauf, wie ich feststellen könnte, wer gewonnen hat.. hätte jemand einen Denkanstoß in die richtige Richtung? Wäre sehr lieb!
Java:
import javax.swing.JApplet;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
public class TicTacToe extends JApplet implements MouseListener{
	
	private Point clickPoint = null;
	public int counter = 0;
	public boolean aa, ab, ac, ba, bb, bc, ca, cb, cc;
	public TicTacToe() {
		
	}
	public void init() {
		addMouseListener(this); // Listener für 'Mouse Events'
		setBackground (Color.white);
	}
	
	public void paint (Graphics g) {

		g.drawLine(25, 80, 175, 80);
		g.drawLine(25, 130, 175, 130);
		g.drawLine(75, 30, 75, 180);
		g.drawLine(125, 30, 125, 180);
		
		if (isFull())
		g.drawString("Das Feld ist voll!", 0, 200);
		
	      if (counter % 2 == 0)
				g.drawString("Spieler X ist an der Reihe", 15,20);
			else 
				g.drawString("Spieler O ist an der Reihe", 15,20); 
		
		if (!aa && clickPoint.getX() > 25 && clickPoint.getX() < 75 && clickPoint.getY() > 30 && clickPoint.getY() < 80) {
			counter++;
			aa = true;
			if (counter % 2 == 0)
				drawCircle(g, 25, 30);
			else
				drawCross(g, 25, 30);
		}
		
		if (!ab && clickPoint.getX() > 25 && clickPoint.getX() < 75 && clickPoint.getY() > 80 && clickPoint.getY() < 130) {
			counter++;
			ab = true;
			if (counter % 2 == 0)
				drawCircle(g, 25, 80);
			else
				drawCross(g, 25, 80);
		}
		
		if (!ac && clickPoint.getX() > 25 && clickPoint.getX() < 75 && clickPoint.getY() > 130 && clickPoint.getY() < 180) {
			counter++;
			ac = true;
			if (counter % 2 == 0)
				drawCircle(g, 25, 130);
			else
				drawCross(g, 25, 130);
		}
		
		if (!ba && clickPoint.getX() > 75 && clickPoint.getX() < 125 && clickPoint.getY() > 30 && clickPoint.getY() < 80) {
			counter++;
			ba = true;
			if (counter % 2 == 0)
				drawCircle(g, 75, 30);
			else
				drawCross(g, 75, 30);
		}
		
		if (!bb && clickPoint.getX() > 75 && clickPoint.getX() < 125 && clickPoint.getY() > 80 && clickPoint.getY() < 130) {
			counter++;
			bb = true;
			if (counter % 2 == 0)
				drawCircle(g, 75, 80);
			else
				drawCross(g, 75, 80);
		}
		
		if (!bc && clickPoint.getX() > 75 && clickPoint.getX() < 125 && clickPoint.getY() > 130 && clickPoint.getY() < 180) {
			counter++;
			bc = true;
			if (counter % 2 == 0)
				drawCircle(g, 75, 130);
			else
				drawCross(g, 75, 130);
		}
		
		if (!ca && clickPoint.getX() > 125 && clickPoint.getX() < 175 && clickPoint.getY() > 30 && clickPoint.getY() < 80) {
			counter++;
			ca = true;
			if (counter % 2 == 0)
				drawCircle(g, 125, 30);
			else
				drawCross(g, 125, 30);
		}
		
		if (!cb && clickPoint.getX() > 125 && clickPoint.getX() < 175 && clickPoint.getY() > 80 && clickPoint.getY() < 130) {
			counter++;
			cb = true;
			if (counter % 2 == 0)
				drawCircle(g, 125, 80);
			else
				drawCross(g, 125, 80);
		}
		
		if (!cc && clickPoint.getX() > 125 && clickPoint.getX() < 175 && clickPoint.getY() > 130 && clickPoint.getY() < 180) {
			counter++;
			cc = true;
			if (counter % 2 == 0)
				drawCircle(g, 125, 130);
			else
				drawCross(g, 125, 130);
		}
		
	}
	
	public void drawCross(Graphics g, int x, int y) {
		g.drawLine(x + 5, y + 5, x + 45, y + 45);
		g.drawLine(x + 45, y + 5, x + 5, y + 45);
	}
	
	
	public void drawCircle(Graphics g, int x, int y) {
		g.drawOval(x + 5, y + 5, 40, 40);
	}
	
	public void mouseClicked (MouseEvent event) {
		// Bestimmt Stelle des Mausklicks und erzwingt 'repaint'
		clickPoint = event.getPoint();
		repaint();
		}

	public void mousePressed (MouseEvent event) {}
	public void mouseReleased (MouseEvent event) {}
	public void mouseEntered (MouseEvent event) {}
	public void mouseExited (MouseEvent event) {}

	
	boolean isFull() {
		if (counter == 8)
			return true;
		else
			return false;		
	}
	
/*	boolean isWon() {
		
	}
*/
}

Beste Grüße,
Jolies
 

Marco13

Top Contributor
Code:
 public void paint (Graphics g) {
    super.paint(g);
...
(oder notfalls ein
Code:
 public void paint (Graphics g) {
    super.paint(g);
    g.setColor(Color.WHITE);
    g.fillRect(0,0,getWidth(),getHeight());
...
)

Die Gewinnabfrage wirst du im Moment nur mit übelst-häßlichen, verschachtelten if-Abfragen lösen können. Du solltest die Booleans vielleicht in einen "new boolean[3][3]"-Array packen, das würde einiges einfacher machen.
 
J

Jolies

Gast
Schon mal vielen Dank für die schnelle Antwort, Problem dabei ist nur, dass so die Kreuze und die Kreise auch bei jedem Klicken wieder gelöscht werden, die sollen ja schon da bleiben...
 
S

SlaterB

Gast
das Array muss unbedingt her, ob boolean (reicht kaum für drei Werte leer, X oder O)
oder sonstwas (z.B. eine eigene Klasse mit Positionsangaben, würde den Zeichencode dramatisch verkürzen, Schleifen!)

im Array dauerhaft alle Informationen speichern, über sämtliche bis zu neun Klicks seit Start,
nicht nur den letzten Klick
 
J

Jolies

Gast
Würde es funktionieren, wenn ich ein new char [3][3] basteln würde und das dann bei jedem klick neu abfrage wo ich was zu zeichnen habe? Eigentlich müsste es dann doch so lange die Inhalte speichern, bis ich das Programm schließe oder?
Müsste mir dann noch überlegen, wie ich die isWon abfrage mache...
 
S

SlaterB

Gast
1. Frage:
bei einem Klick musst du genau das tun, was in dieser Situation zu tun ist, etwa ausrechnen welches Feld gemeint ist, schauen ob in dem Feld schon was drin steht, X oder O eintragen oder Fehlermeldung/ nix tun,
gegebenenfalls alles neuzeichen,

das sind normale Dinge die man so tut, 'neu abfrage wo ich was zu zeichnen habe' klingt eher komisch, aber auch so allgemein dass damit durchaus irgendwas richtiges gemeint sein kann

2. Frage:
gewisst muss vor dem Programmende irgendwas gespeichert werden,
ja, wenn dich dafür eine Bestätigung weiterbringt


ausführliche genaue sinnvolle Fragen, mit Code und Beispielen, ach das wäre schön..,
immer bedenklich wenn die Antwortposts länger sind als die Fragen ;)
 
J

Jolies

Gast
Tut mir leid, ich war gerad nur recht angetan von der Idee und wollte schnell anfangen, wenn ich so drauf bin verwirre ich immer gerne alles und jeden, besonders mich. =)

Also ich hatte mir überlegt, eine neue Klasse anzulegen, in der ich ein char[][] definiere, in dem ich zuerst alle Positionen auf Leerzeichen setze. Wenn in ein Feld geklickt wird, sehe ich nach an welcher Position geklickt wurde. An der entsprechenden Stelle wird dann in dem char[][] nachgesehen. Falls dort ein Leerzeichen ist, wird die Position auf X/O gestellt. Dann wird in der TicTacToe-Klasse das Array abgetastet und für das X/O das jeweilige Zeichen gemalt. So kann ich bei jedem Durchlauf alles neu Zeichnen lassen, und habe auch nicht mehr das Problem, dass sich die Schrift nicht richtig darstellen lässt.
Meine Frage war jetzt eigentlich, ob das überhaupt Sinn macht, dass so zu tun, oder ob ich viel zu kompliziert / falsch denke.. =)
 
J

Jolies

Gast
So, ich hab mich an der ganzen Sache noch einmal versucht, aber ich komm einfach nicht weiter.. hier erstmal der Code:

Die erste Klasse, in der das Array erstellt und die Position erfasst wird:
Java:
public class Cell {
	
	private char pos [][];
	TTT clickPoint = new TTT();
	public int count = 0;
	
	Cell (char pos[][]) {
		this.pos = pos;
	}
	
	int getX() {
		
		if (clickPoint.getX() > 25 && clickPoint.getX() < 75) 
			return 0;
		if (clickPoint.getX() > 75 && clickPoint.getX() < 125)
			return 1;
		if (clickPoint.getX() > 125 && clickPoint.getX() < 175)
			return 2;
		else
			return -1;		
	}
	
	int getY() {
		if (clickPoint.getY() > 30 && clickPoint.getY() < 80)
			return 0;
		if (clickPoint.getY() > 80 && clickPoint.getY() < 130)
			return 1;
		if (clickPoint.getY() > 130 && clickPoint.getY() < 180)
			return 2;
		else
			return -1;
	}
	
	public void setPos (int x, int y, char wert) {
		x = getX();
		y = getY();
		
		if (x < 0 || y < 0)
			return;
			
		if (count % 2 != 0) {
			wert = 1;
			setPos(x, y, wert);
			count++;
		}
		else {
			wert = 2;
			setPos(x, y, wert);
			count++;
		}
	}
	
	char getPos (int x, int y) {
		return pos[x][y];
	}
}

Und hier die zweite Klasse, in der gezeichnet werden soll:
Java:
import javax.swing.JApplet;
import java.awt.*;
import java.awt.event.MouseEvent;werd ich ganz depressiv wenn ich programmiere.. =\
import java.awt.event.MouseListener;
public class TTT extends JApplet implements MouseListener{
	
	public Point clickPoint = null;
	public int counter = 0;
	public int x, y;
	Cell pos[][] = new Cell [x][y];
	
	public void init() {
		addMouseListener(this); // Listener für 'Mouse Events'
	}
	
	public void mouseClicked (MouseEvent event) {
		// Bestimmt Stelle des Mausklicks und erzwingt 'repaint'
		clickPoint = event.getPoint();
		
		repaint();
	}
	


	public void paint (Graphics g) {
		//übermalen des alten Feldes
		super.paint(g);

		setSize (500, 500);

		g.drawLine(25, 80, 175, 80);
		g.drawLine(25, 130, 175, 130);
		g.drawLine(75, 30, 75, 180);
		g.drawLine(125, 30, 125, 180);
	      
	    for (int i = 0; i < 2; i++) {
	    	for (int j = 0; j < 2; j++) {
	    		if (pos[i][j].getPos(i, j) == 1)
	    			drawCross(g, 1, 2);
	    	}
	    }
	}
	
	public void drawCross(Graphics g, int x, int y) {
		g.drawLine(x + 5, y + 5, x + 45, y + 45);
		g.drawLine(x + 45, y + 5, x + 5, y + 45);
	}
	
	
	public void drawCircle(Graphics g, int x, int y) {
		g.drawOval(x + 5, y + 5, 40, 40);
	}

	public void mousePressed (MouseEvent event) {}
	public void mouseReleased (MouseEvent event) {}
	public void mouseEntered (MouseEvent event) {}
	public void mouseExited (MouseEvent event) {}

}

Aber blöderweise malt das Programm außer dem Spielfeld leider nichts.. hab ich die Klassen vielleicht nicht ordentlich verknüpft? Manchmal mach mich das Programmieren richtig depressiv.. =\

niedergeschlagene Grüße,
Jolies
 

Michael...

Top Contributor
Üblicher Weise überschreibt man nicht direkt die paint() von JApplet oder JFrame, sondern überschreibt die paintComponent einer JComponent oder eines JPanels und fügt diese in den Container.
Warum benötigt die Klasse Cell eine Variable vom Typ TTT?
Wozu die Variable count?
Es muss vor dem Setzen geprüft werden, ob das Feld nicht bereits gesetzt werden.
Ausserdem wird in die Felder von pos nie etwas gesetzt.
Die Methode setPos() ruft sich selbst auf, das ist 1. nicht notwendig und 2. würde das in dem Fall zu einer Endlosschleife führen.

In der Klasse TTT wird ein Array vom Typ Cell angelegt, er wird aber nur ein Objekt vom Typ Cell benötigt.
Die Koordinaten des Mausklicks werden zwar in einer VAriablen gespeichert, aber sonst passiert nichts - ausser das ein Neuzeichnen (obwohl es nichts neues zum Zeichnen gibt) angefordert wird.

Und dann noch bitte beachten: '1' == 1 liefert false

Fazit: Grundlagen, Grundlagen und nochmal Grundlagen....
 
J

Jolies

Gast
Üblicher Weise überschreibt man nicht direkt die paint() von JApplet oder JFrame, sondern überschreibt die paintComponent einer JComponent oder eines JPanels und fügt diese in den Container.
Ich weiß leider nichts, was du damit meinst.. könntest du mir bitte ein Beispiel zeigen oder so? =0

Die Varible von TTT brauch ich doch, um festzustellen wo geklickt wurde, oder nicht? Oder kann ich von der TTT-Klasse direkt die Methoden von Cell aufrufen?
Die Endlosschleife von setPos ist wirklich einfach nur dämlich, wie ich geschafft hab, dass zu übersehen... omg
 
S

SlaterB

Gast
räum doch einfach der Reihe nach auf:
Cell braucht kein TTT, Cell braucht kein count, kein Array, vielleicht eine einzelne char-Variable 'zustand'
dazu einfach nur set und get, in der ganzen Klasse muss kein einziges x/y auftauchen,
extrem gesehen könnte Cell ganz weg und TTT ein char-Array verwenden

aber ne eigene Klasse Cell ist schon schön, ruhig behalten,
dort könnte die Position x/y zum Zeichnen gespeichert werden, aber das hast du ja in den paint-Methoden in etwa drin, reicht auch

die großen Auswertungen, aus Mausposition x/y berechnen usw., das gehört in die TTT-Klasse, diese muss ja entscheiden, welche der 9 Zellen betroffen ist,
Cell kann das nicht machen,
 
J

Jolies

Gast
Okay, hab Cell und TTT jetzt mal aufgeräumt. Mit der Abfrage der Postion usw klappt wie gewohnt alles, ich verstehe aber leider nichts, wie ich die verschiedenen postionen in einem char speichern kann, ohne dem ganzen Koordinaten mit zu geben, um zu wissen, wo was ist..

Mein Ansatz dazu wäre jetzt gewesen das hier in Cell
Java:
	Cell(char typ, int x, int y) {
		xPos = x; 
		yPos = y; 
		setZustand(typ);
	} 
	
	void setZustand(char typ) {
		t = typ;
		}

und das hier in TTT zu machen:
Java:
	Cell c1 = new Cell(1,1);
	Cell c2 = new Cell(1,2);
	Cell c3 = new Cell(1,3); ...usw

Ich hätte dann aber wieder x / y, von denen du meintest, dass ich sie in cell nicht mehr bräuchte.
 
S

SlaterB

Gast
1,1 1,2 1,3 bringen in Cell nun wirklich nichts, oder was willst du damit?
char typ brauchst du im Konstruktor auch nicht, alle Zellen sind am Anfang gleich,
deine Aufrufe haben eh nur zwei Werte?
poste doch wenigstens Code der zusammenpasst..

in TTT ist nach wie vor die wichtigste Grundentscheidung, ein Array für die 9 Zellen anzulegen,
wieso gehst du jetzt wieder auf Einzelvariablen?
Java:
Cell c[][] = new Cell [3][3];
for (int i = 0; i < 3; i++) {
      for (int j = 0; j < 3; j++) {
             c[i][j] = new Cell(); // keinerlei Parameter nötig
       }
}
simpelster Code, noch passiert ja auch gar nix, nur 9 Zellen sind da, wo vorher nichts war,

wann immer du irgendwas machen willst wie x,y im Konstruktor, ist es schon ein gewisser Vorteil, auch zu überlegen wofür das gut ist,
woran ich dachte war, die Mal-Positionen 25, 50, 75 usw. in den Zellen abzulegen, damit die paint-Methode das pro Zelle direkt auslesen kann,
aber wie gesagt hast du ja paint-Schleifen, die das vom Array-Index aus machen, das funktioniert auch,
(edit: noch nicht wirklich in deinem Code weiter oben, aber grundsätzlich denkbar, man kann in einer Schleife index*25 rechnen usw)

dort bei den Schleifen hast du dann auch die Indexe 1,1 1,2 1,3 usw., deswegen bringt es nicht so viel, die in der Zelle abzulegen,
um eine Zelle auszuwählen muss man vorher sowieso die Indexe kennen
 
Zuletzt bearbeitet von einem Moderator:

jgh

Top Contributor
@ Jolies
wenn es nicht unbedingt ein Applet sein muss...
hier ist ansonsten eine Swing-Applikation, die einfach 9 JButtons auf einen JFrame packt und abwechselnd nach Mausklick ein "X", oder ein "O" auf den Button setzt und den Button anschließend disabeld.

Unten habe ich noch eine Beispielfunktion geschrieben, wie du evtl. überprüfen kannst...ob jemand gewonnen hat. Die Reihenfolge ist von links nach rechts, dann von unten nach oben (von 0-8)

Wann, oder ob überhaupt gewonnen worden ist, müsstest du natürlich noch implementieren.
Im Moment macht das Programm nichts...man kann 9x klicken und dann ist vorbei.

Java:
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;

public class TicTacToe {

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

	private JFrame frame;
	private JButton[] buttons;
	private boolean isX;

	public TicTacToe() {
		isX = true;
		initFrame();
		initButtons();
		frame.setVisible(true);

	}

	private void initButtons() {
		buttons = new JButton[9];
		int i;
		for (i = 0; i < 9; i++) {
			buttons[i] = new JButton();
			frame.add(buttons[i]);
			buttons[i]
					.addActionListener(new TicTacToeListener(this, buttons[i]));
		}
	}

	private void initFrame() {
		frame = new JFrame("Tic Tac Toe");
		frame.setSize(250, 250);
		frame.setLayout(new GridLayout(3, 3));
		frame.setLocationRelativeTo(null);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}

	public boolean isX() {
		return isX;
	}

	public void setxOrO(boolean xOrO) {
		this.isX = xOrO;
	}

	public JButton[] getButtons() {
		return buttons;
	}

}

class TicTacToeListener implements ActionListener {

	private TicTacToe ticTacToe;
	private JButton button;
	private Font font;

	public TicTacToeListener(TicTacToe ticTacToe, JButton button) {
		this.ticTacToe = ticTacToe;
		this.button = button;
		font = new Font("SansSerif", Font.BOLD, 50);

	}

	@Override
	public void actionPerformed(ActionEvent e) {
		if (e.getSource() == button) {
			if (ticTacToe.isX()) {
				button.setText("X");

			} else {
				button.setText("O");
			}
			button.setFont(font);
			button.setEnabled(false);
			changeXorO();
			beispielMethode();
		}

	}

	public void beispielMethode() {
		JButton[] jb = ticTacToe.getButtons();
		for (int i = 0; i < jb.length; i++) {
			if (jb[i].isEnabled()) {
				System.out.println("Feld " + i + " ist frei");
			} else {
				if (jb[i].getText().equals("X")) {
					System.out.println("Feld " + i + " ist X");
				} else {
					System.out.println("Feld " + i + " ist O");
				}
			}
		}
	}

	private void changeXorO() {
		if (ticTacToe.isX()) {
			ticTacToe.setxOrO(false);
		} else {
			ticTacToe.setxOrO(true);
		}
	}
}
 
Zuletzt bearbeitet:

Guardi

Bekanntes Mitglied
Hi erstmal,

ich persönlich finde ja dieses Hantieren mit den Arrays ganz furchtbar. In Java gibt es einfach Datenstrukturen die bereits bestens für die Benutzung in Algorithmen geeignet sind:

Lesson: Introduction to Collections (The Java™ Tutorials > Collections)

Man könnte z.B. die JButtons in einer Collection ablegen, sinnvolle IDs vergeben und drüber Iterieren (Iterieren = über eine Datenstruktur "laufen"). Collections muss man sich selbst als Anfänger, meiner Meinung nach, gleich nach dem Grundverständnis der OOP sofort anschauen. Eins der wichtigsten Frameworks überhaupt.
 
S

SlaterB

Gast
über ein Array kann man auch iterieren, was ist der Unterschied?
ob mit i-Index oder for-each, alles gleich

nur wenn man viele Arrays bzw. sonstige Container allgemein verarbeiten will, etwa an Collections.sort() übergeben,
oder wenn man an einer Stelle kurz die Implementierung austauschen will, ohne den Rest des Programmes zu ändern,
dann bringt die Objektorientierung mit Interfaces Vorteile,
von veränderlicher Größe mal ganz abgesehen

in beschränkten kleinen Bereichen arbeitet ein Array tadellos, wie sollte sonst z.B. eine ArrayList implementiert werden
 

Marco13

Top Contributor
Grundsätzlich stimme ich besonders dem letzten Teil zu. Insbesondere weil die Collections ja DIE Paradebeispiele für viele OOP-Konzepte schlechthin sind. Aber bei TicTacToe ist ein [3][3]-Array IMHO schon OK: Intutiv und leicht zu verwenden (eine List<List<X>> ist da unhandlicher), und die Größe des Spielfeldes ändert sich auch nicht ;) Wenn es um ein ... wie soll man sagen... "höheres Level" geht, würde man ohnehin ein interface für ein Modell definieren, und das GUI ... könnte (oder sollte) dann auch mit hübsch gerenderten Kreuzchchen (d.h. OHNE Buttons!) gemacht werden...
 

Neue Themen


Oben