Rudimentäres Spielfeld generieren in Java?
Hallo Com,
ich habe die Aufgabe, ein rudimentäres Spielfeld zu erstellen, dessen Größe (H,B) über „args“ vom User gesteuert wird. Es soll durch ein 2-dimensionales char-Array kreiert werden. Leere Felder sollen mit Leerzeichen gefüllt werden. Das ganze in Java.
Opfert jemand seine wertvolle Zeit, um mir zu helfen? Ich fühle mich so überrannt :D
Danke!
2 Antworten
Zuerst überprüfst du, ob du auch genau zwei Argumente übergeben bekommst
if(args.length() != 2){ System.out.println("Zu wenig Argumente"); System.exit(0); }
du bekommst über args zwei Integer übergeben
int width = 0; int height = 0; try{ width = Integer.parseInt(args[0]); height = Integer.parseInt(args[1]); } catch (NumberFormatException e){ System.out.println("Falsche Eingabe"); System.exit(0); }
Jetzt kannst du entweder ein 1D-Array erstellen (und musst den Zugriffsindex anders berechnen) oder du nimmst ein 2D-Array. Ersteres hat bessere Laufzeiten, daher nehme ich das und fülle es mit Leerzeichen
char[] data = new char[width*height]; for (int i = 0; i < data.length(); i++){ data[i] = ' '; }
Für die Berechnung des Indizes kannst du folgende Methode nutzen (wenn du die Row-Major-Order nutzt) - unter der Annahme, dass du a und b nicht nur lokal, sondern global gespeichert hast und alles statisch (ohne Objekt) abläuft:
public static int getIndex(int i, int j){ if (i < 0 || j < 0 || i >= width || j >= height){ throw new IndexOutOfBoundsException(); } return width*j+i; }Muss natürlich dann geprüft werden, ob i und j valide sind - ich hab hier sonst eine Exception werfen lassen;
alternativ über ein 2D-Array:
char[][] data = new char[width][height]; for(int i = 0; i < width; i++){ for (int j = 0; j < height; j++){ data[i][j] = ' '; } }
System.out.println("Zu wenig Argumente");
sollte natürlich folgendes sein, da ich nur darauf schaue, ob zwei Argumente übergeben wurden:
System.out.println("Unpassende Anzahl an Argumenten");
Punkt 1) Parsen der Argumente
Die Kommandozeilenargumente kommen als Strings. Du musst also ints daraus machen. Gehen wir davon aus, dass im ersten Aufrufparameter die Höhe gegeben wird und im zweiten die Breite, dann sieht das in etwa so aus:
int h = Integer.valueOf(args[0]);
int b = Integer.valueOf(args[1]);
Achtung: Es müsste eigentlich validiert werden, ob wirklich 2 Parameter da sind und diese auch wirklich ints sind. Das spare ich mir.
So. Was brauchst Du jetzt? Ein zweidimensionales Array für das Spielfeld mit der gegebenen Größe:
char[][] field = new char[h][b];
Und jetzt füllst Du das mit Leerzeichen:
for (int i = 0; i < h; i++)
for (int j = 0; j < b; j++)
field[i][j] = ' ';
Ist es wirklich merklich schneller, wenn der Programmierer die Multiplikationen mit der anderen Dimension vornimmt, als wenn man sich den Compiler darum kümmern lässt? (Ein guter Compiler merkt sich ja bereits berechnete Werte, wenn er mitkriegt, dass die verwendeten Variablen zwischen den Berechnungen nicht ändern.)
Bezüglich "parseInt" hast Du Recht. Es mag auch sein, dass die Laufzeit bei einem eindimensionalen Array besser ist, aber das ist bei den vermutlich gegebenen Größe a) nicht relevant und b) für einen Anfänger, der mit dieser Aufgabe an sich schon überfordert ist, unmöglich zu implementieren ;-)
Bei C++ wird intern beides in eine LEA-Instruktion übersetzt, zumiindest auf einer x86-Plattform.
Bei Java ist die Methode mit doppeltem Index aber tatsächlich schneller und die händische Berechnung vom Index erzeugt mehr Bytecode!
Wenn wir also folgenden Code nehmen:
final int wdt = 8;
final int hgt = 6;
final int len = wdt * hgt;
int buf1[] = new int[len];
int buf2[][] = new int[hgt][wdt];
Und in einer geschachtelten Doppel-Schleife auf die Elemente von buf1 und buf2 wie folgt zugreifen:
buf1[y * wdt + x] = 2;
buf2[y][x] = 3;
Dabei erzeugt ersterer Zugriff mehr Instruktionen, und letzterer weniger. Was der JIT-Compiler letztendlich daraus macht, müsste man messen, aber da habe ich jetzt keine Lust zu. :)
Hier ist das Disassemblat:
41: aload 4
43: iload 6
45: bipush 8
47: imul
48: iload 7
50: iadd
51: iconst_2
52: iastore
// ...
58: aload 5
60: iload 6
62: aaload
63: iload 7
65: iconst_3
66: iastore
imul und bipush sind auch relativ teure Operationen, sodass ich mich nicht wundern würde, wenn die zweite Variante tatsächlich merklich schneller wäre. (Aber wie "ohwehohach" schon geschrieben hat, spielt das im vorliegenden Beispiel sowieso keine Rolle.)
Allerdings sollte man über größere 2D-Arrays so iterieren, dass man der "natürlichen" Speicherauslegung folgt, sonst ruiniert man sich die Laufzeit, weil ständig alles neu in den Cache geladen werden muss.
Sprich so:for (int y = 0; y < hgt; ++y) {Und auf keinen Fall so:
for (int x = 0; x < wdt; ++x) {
foobar(buf[y * wdt + x]);
}
}
for (int x = 0; x < wdt; ++x) {
for (int y = 0; y < hgt; ++y) {
foobar(buf[y * wdt + x]);
}
}
Aber das weißt du ja selbst, deshalb richtet sich der Hinweis an die anderen Mitleser. :)
Ich würde anstelle von "valueOf" einfach "parseInt" nutzen (was valueOf intern auch tut, aber ein Integer-Objekt statt einen primitiven int zurückgibt
oder ein eindimensionales Array für bessere Laufzeiten