Dringend Hilfe: Java Code?
Kann jemand mir diese Java-Code erklären? Ich komme mit dem Code nicht zurecht. Wie kann man noch einfacher schreiben?
Die Aufgabenstellung: Verwenden Sie die Bit-Operationen von Java, um den Zustand einer Zelle des Spielfeldes in jeweils zwei Bits der int-Variablen abzulegen (warum gerade zwei?).
/**
* Write a description of class SpielfeldInteger here.
*/
public class SpielfeldInteger implements Spielfeld
{
private int spielInt;
/**
* Initialisiert ein neues, leeres Spielfeld.
*/
public SpielfeldInteger() {
spielInt = 0x00000;//2AAAA
// 0000 0000 0000 0000 0000;
}
/**
* Gibt den Besitzer der angegebenen Position auf dem Spielfeld.
*
* @param zeile vertikale Position (0-2)
* @param spalte horizontale Position (0-2)
* @return 0 (unbesetzt), 1 (Spieler 1), 2 (Spieler 2)
*/
public int gibBesitzer(int zeile, int spalte) {
int schiebung = (zeile*3*2)+(spalte*2);
int geschobenerInt = spielInt>>>schiebung;
//System.out.println(Integer.toBinaryString(geschobenerInt));
//System.out.println((geschobenerInt&3));
if((geschobenerInt&3) == 2) {
return 1;
} else if((geschobenerInt&3) == 3) {
return 2;
} else {
return 0;
}
}
/**
* Besetzt die angegebene Position auf dem Spielfeld fuer einen Spieler.
*
* @param zeile vertikale Position (0-2)
* @param spalte horizontale Position (0-2)
* @param spieler 0 (leer), 1 (Spieler 1), 2 (Spieler 2)
*/
public void besetzePosition(int zeile, int spalte, int spieler) {
int setzung;
if(spieler == 1 || spieler == 2) {
setzung = (spieler+1)<<(zeile*3*2)+(spalte*2);
spielInt = spielInt|setzung;
}
else if(spieler == 0) {
setzung = ~(3<<(zeile*3*2)+(spalte*2));
spielInt = spielInt&setzung;
}
}
/**
* Gibt an, ob das Spielfeld an allen Positionen belegt ist.
*/
public boolean istVoll() {
return ((spielInt&0x2AAAA) == 0x2AAAA);
}
}
1 Antwort
Du hast ein 3×3-Feld. Das kann man als lineares Feld der Länge 9 abbilden. Ein Wert in Zeile z und Spalte s im 3×3-Feld liegt dann z. B. am Index
int index ( int z, s ) {
return =z*3+s;
}
Die erlaubten Werte im Feld sind {0, 1, 2}. Dazu braucht man mindestens 2 Bit.
Das Feld wird nun in einem einzigen Integer gespeichert: Die untersten 2 Bit gehören zu Zelle 0, die nächsten 2 zu Zelle 1 usw. Das reicht in Java für 16 Zellen.
Du kannst eine Zelle i auslesen, indem Du das Feld um 2*i nach rechts schiebst und dann den Wert mit der Zellenbreite 0b11 maskierst (damit die anderen Zellenwerte verschwinden):
int get ( int index ) {
return ( feld >> 2*index ) & 0b11;
}
Du kannst einen neuen Wert in eine leere Zelle schreiben, indem Du den Wert um 2*i nach links verschiebst und dann das Feld damit veroderst:
void set ( int index, int value ) {
feld |= value << 2*index;
}
Mit Oder kann man aber nur Bits setzen, nicht löschen. Deshalb sollte man vor dem Überschreiben die Zelle erst mal auf 0 setzen (das wurde in Deinem Code komplett übersehen). Dazu verschiebst Du die Zellmaske 0b11 an die richtige Position (z.B. 0..011000), negierst diesen Wert (⇒ 1..1001111) und verundest ihn mit dem Feld:
void clear ( int index ) {
feld &= ~(0b11 << 2*index);
}
Dein Code schreibt übrigens die Werte 0=frei, 2=spieler1 oder 3=spieler2 in das Feld. Das hat den Vorteil, dass man schneller prüfen kann, ob das Feld voll ist, weil dann alle 9 Zellen das High-Bit gesetzt haben (0x2AAAA=0b101010_101010_101010). Dafür muss man bei jedem Lesen und Schreiben eine Fallunterscheidung machen. Ich finde das unnötig kompliziert.
am Besten, indem man diese verkorksten 0/2/3-Werte wegwirft. Ich schreibe stattdessen mal (spieler+1) rein (also 1/2/3):
public int gibBesitzer(int zeile, int spalte) {
int index = zeile*3+spalte;
return (spielInt>>>2*index & 0b11) - 1;
}
public void besetzePosition(int zeile, int spalte, int spieler) {
int index = zeile*3+spalte;
spielInt &= ~(0b11 << 2*index); // clear
spielInt |= (spieler+1) << 2*index;
}
Oder mit den Hilfsmethoden aus meiner Antwort:
public int gibBesitzer(int zeile, int spalte) {
return get(index(zeile, spalte)) - 1;
}
public void besetzePosition(int zeile, int spalte, int spieler) {
int index = index(zeile, spalte);
clear(index);
set(index, spieler+1);
}
Ich finde das lesbarer.
Was ist hier 0b11? Was haben Sie für Konstruktur geschrieben, gleiche wie ich?
Kannst du bitte hier einzelnen Zeile ausführlich erklären, was genau passiert ist? Ich verstehe nicht was 0b11 ist und woher kommt es? Warum -1?
0b11 = 11 binär = 3 dezimal.
Warum -1? Das Feld soll die Werte 0 (=leer) oder 1 oder 2 (spieler) enthalten. In deiner Implementierung wird das durch umständliche Fallunterscheidungen als 0 (=leer) oder 2 oder 3 (spieler+1) geschrieben, damit istVoll() einfacher wird. Ich halte das für unnötig kompliziert.
Mein Vorschlag verwendet als Kompromiss die Werte 1 (=leer) oder 2 oder 3 (spieler+1). Damit entfallen die Fallunterscheidungen. Ich schreibe stattdessen immer den Wert spieler+1 und gebe den Wert−1 als Besitzer zurück.
Man könnte das Feld nun im Konstruktor mit 0x15555 = 0b010101_010101_010101 initialisieren, oder jeden Besitzer≤0 (statt ==0) als freies Feld akzeptieren.
Kannst du bitte hier einzelnen Zeile ausführlich erklären
Die Methoden get(), set() und clear() habe ich schon in meiner Antwort erklärt. noch ausführlicher schaffe ich es nicht.
Damit werden alle 9 Plätze mit 01 (=leer) initialisiert. In der Binärdarstellung ist es leichter zu erkennen.
Warum 2*index
Weil für jede Position 2 Bits gebraucht werden. Für Platz Nr. 5 muss man um 10 Bit verschieben.
Bmbedeutet 01 LEER?
Eigentlich sollen die Werte 0=leer oder 1/2=Spieler geschrieben werden. Dein Programm zählt bei den Spielern Eins dazu, damit sich leichter prüfen lässt, ob alle Plätze belegt sind. Die Spielerwerte 2 und 3 (binär 10 und 11) haben nämlich das obere Bit gesetzt.
Ich habe das vereinfacht, indem ich immer Eins addiere (und beim Auslesen subtrahiere). Jetzt wird also die 0 als 1 gespeichert. Die Prüfung auf das obere Bit klappt hier genauso.
Und warum &0b11?
Beim Verschieben der Bits von spielInt fallen ja nur die Positionen vor der gewünschten weg. die Positionen dahinter sind noch da und müssen gelöscht werden, damit nur der Inhalt an der gewünschten Position übrig bleibt:
8 . 7 . 6 .. 5 . 4 . 3 .. 2 . 1 . 0 <--- index
01 01 11 10 01 10 11 10 01 | Shift (>> 2*3)
00 00 00 01 01 11 10 01 10 | Mask (& 0b11)
00 00 00 00 00 00 00 00 10
3 == 0b11. Der Programmierer war hier wohl nur zu faul zum Tippen.
Ich bin ja schon froh, dass er in istVoll() wenigstens eine Hex-Zahl (0x2AAAA) und nicht dezimal 174762 geschrieben hat.
Wenn es um die Bitpositionen in einer Zahl geht, sind Konstante im Binärformat (0b...) lesbarer, obwohl sie länger sind. Bei 0b10_10_10_10_10_10_10_10_10 sieht man nämlich sofort, welche Bits gemeint sind, bei 0x2AAAA kann man es mit etwas Übung erkennen, aber bei 174762 brauchen die meisten einen Taschenrechner.
Woher kommt 3? Warum 3 und nicht 2 oder 1?
Die 3 ist die Bitmaske, in der nur die Bits der Zelle 0 gesetzt sind. nochmal ein Beispiel, wie man an den Wert in Zelle 5 kommt:
8 . 7 . 6 .. 5 . 4 . 3 .. 2 . 1 . 0 <--- index
01 01 11 10 01 10 11 10 01 | Shift (>> 2*5)
00 00 00 00 00 01 01 11 10 | Inhalt der Zellen 5-8
00 00 00 00 00 00 00 00 11 | Maske 0b11
00 00 00 00 00 00 00 00 10 | Wert = Inhalt & Maske
benutzen wir immer die Maske 3, es ist fest? Woher sollen wir wissen, dass wir die Maske 3 schreiben sollen und nicht die Maske 22, 4 oder 1?
Welche Rolle spielt hier die Operator ~ ? KANNST DU DEN Code nochmal erklären?
else if(spieler == 0) {
setzung = ~(3<<(zeile*3*2)+(spalte*2));
spielInt = spielInt&setzung;
Damit wird eine Zelle gelöscht (=auf 0 gesetzt). Dazu wieder ein Beispiel für Zelle 5:
8 . 7 . 6 .. 5 . 4 . 3 .. 2 . 1 . 0 <--- index
00 00 00 00 00 00 00 00 11 | Maske 0b11 (= 3)
00 00 00 11 00 00 00 00 00 | Maske << 2*5
11 11 11 00 11 11 11 11 11 | negiert (~) -- im Code „setzung“
01 01 11 10 01 10 11 10 01 | altes Spielfeld
01 01 11 00 01 10 11 10 01 | Spielfeld & neg
Das Ergebnis ist ein Spielfeld, bei dem nur die Bits von Zelle 5 gelöscht wurden.
Also wenn spieler gleich 0 ist, dann schieben wir erstmal nach rechts und dann löschen wir die Zelle. Sagt der Code das?
/**
* Gibt den Besitzer der angegebenen Position auf dem Spielfeld.
*
* @param zeile vertikale Position (0-2)
* @param spalte horizontale Position (0-2)
* @return 0 (unbesetzt), 1 (Spieler 1), 2 (Spieler 2)
*/
public int gibBesitzer(int zeile, int spalte) {
int index= zeile*3+spalte;
int schiebung = 2*(zeile*3+spalte);
int geschobenerInt = spielInt>>schiebung;
if((geschobenerInt&3) == 0b01) { //0b01= 1 Dezimal
return 1;
} else if((geschobenerInt&3) == 0b10) { //0b10 = 2 Dezimal
return 2;
} else {
return 0;
}
}
/**
* Besetzt die angegebene Position auf dem Spielfeld fuer einen Spieler.
*
* @param zeile vertikale Position (0-2)
* @param spalte horizontale Position (0-2)
* @param spieler 0 (leer), 1 (Spieler 1), 2 (Spieler 2)
*/
public void besetzePosition(int zeile, int spalte, int spieler) {
int setzung;
if(spieler == 1 || spieler == 2) {
setzung = spieler <<2*(zeile*3+spalte);
spielInt = spielInt|setzung;
}
else if(spieler == 0) {
setzung = ~(3<<2*(zeile*3+spalte));
spielInt = spielInt&setzung;
}
}
/**
* Überprüft ob das Spielfeld komplett voll ist
* return boolean true falls Spielfeld voll ist
*/
public boolean istVoll() {
for(int zeile = 0; zeile<3; zeile++) {
for(int spalte = 0; spalte<3; spalte++) {
if(gibBesitzer(zeile, spalte) == 0) {
return false;
}
}
}
return true;
}
}
00 00 00 00 00 00 00 00 11 | Maske 0b11 (= 3)
00 00 00 11 00 00 00 00 00 | Maske << 2*5
11 11 11 00 11 11 11 11 11 | negiert (~) -- im Code „setzung“
01 01 11 10 01 10 11 10 01 | altes Spielfeld
01 01 11 00 01 10 11 10 01 | Spielfeld & neg
Oder hier:
8 . 7 . 6 .. 5 . 4 . 3 .. 2 . 1 . 0 <--- index
01 01 11 10 01 10 11 10 01 | Shift (>> 2*5)
00 00 00 00 00 01 01 11 10 | Inhalt der Zellen 5-8
00 00 00 00 00 00 00 00 11 | Maske 0b11
00 00 00 00 00 00 00 00 10 | Wert = Inhalt & Maske
nein, der Test zeigt Fehler, ich muss unbedingt (spieler*1) und ==3 für spieler 2 schreiben, damit der Code funktioniert
Die Beispiele zeigen, wie man Bits an einer bestimmten Position setzt bzw. löscht. Das gilt ganz allgemein.
nein, der Test zeigt Fehler
Welcher Test? Welcher Fehler? Ich glaube nicht, dass ich etwas übersehen habe.
Das einzige Problem (auf das ich schon in meiner Antwort hinwies) tritt auf, wenn Du einen Spieler≠0 auf eine Position setzt, die nicht 0 (leer) ist: Dabei werden nämlich nur Bits auf 1 gesetzt (mit Oder), aber nie auf 0 (dafür braucht man Und).
Der Code aus Deiner Frage funktioniert nicht, wenn man eine Position von Spieler 2 (alter Wert=3=0b11) mit Spieler 1 (neuer Wert=2=0b10) überschreiben will. Dann bleibt nämlich Spieler 2 drin.
Im neuen Code funktioniert das auch nicht, und zusätzlich scheitert der Fall, Spieler 1 mit Spieler 2 zu überschreiben. In beiden Fällen steht danach eine 3=0b11 drin, und diesen Spieler gibt es gar nicht.
Zur Abhilfe sollte man die Position immer erst (mit &) löschen und dann (mit |) neu beschreiben:
public void besetzePosition(int zeile, int spalte, int spieler) {
int setzung;
// Alten Wert löschen:
setzung = ~(3<<2*(zeile*3+spalte));
spielInt = spielInt&setzung;
// Neuen Wert schreiben:
setzung = spieler <<2*(zeile*3+spalte);
spielInt = spielInt|setzung;
}
Wie Du siehst, braucht man dann keine Fallunterscheidung mehr.
Warum mit den Wert mit der Zellenbreite 0b11 maskierst? woher kommt 0b11? Wie kommen Sie auf 3 und nicht 2 oder 4?
Wie kommen Sie auf 3 und nicht 2 oder 4?
Um 3 verschiedene Werter zu speichern, braucht man mindestens zwei Bit. Deshalb hat die Maske für einen Zellenwert hier zwei Bits gesetzt.
Für 4 Spieler + leer bräuchtest Du schon 3 Bit. Dann wäre die Maske drei Bit lang: 0b111 (=7).
Wie kann man die Methoden gibBesitzer und besetztePosition noch einfacher schreiben?