Rudimentäres Spielfeld generieren in Java?

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] = ' ';
  }
}
xxxcyberxxx  17.11.2017, 11:56
 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");
0

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] = ' ';
xxxcyberxxx  17.11.2017, 11:56

Ich würde anstelle von "valueOf" einfach "parseInt" nutzen (was valueOf intern auch tut, aber ein Integer-Objekt statt einen primitiven int zurückgibt

Ein zweidimensionales Array

oder ein eindimensionales Array für bessere Laufzeiten

1
PWolff  17.11.2017, 12:24
@xxxcyberxxx

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.)

1
ohwehohach  17.11.2017, 12:25
@xxxcyberxxx

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 ;-)

2
TeeTier  17.11.2017, 15:15
@PWolff

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) {
for (int x = 0; x < wdt; ++x) {
foobar(buf[y * wdt + x]);
}
}
Und auf keinen Fall so:
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. :)

0
TeeTier  17.11.2017, 15:38
@PWolff

PS: Habe doch noch mal mit einem Profiler nachgemessen, und die Variante mit dem Doppel-Index verbrät tatsächlich 50% mehr Zeit, als die mit dem händischen Index.

Der JIT-Compiler scheint also irgendwelche wundersamen Dinge zu Zaubern. :)

1