Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   zweidimensionale Arrays (https://www.delphipraxis.net/208109-zweidimensionale-arrays.html)

Mo53 10. Jun 2021 12:58

zweidimensionale Arrays
 
Hallo,

Ich muss jetzt für die Uni das Spiel Minesweeper nachbauen.

Hier einmal die Anforderungen:

Das bekannte Spiel Minesweeper soll von Euch nachgebaut werden.
Zunächst wird dabei jeweils das Spielfeld mit allen noch verdeckten Zellen sowie den bereits aufgedeckten Zahlen und Bomben in der Konsole ausgegeben. Der Spieler gibt dann die Spalte und danach die Zeile (jeweils ab 1) an (in zwei getrennten Eingaben), in die er "klicken" will sowie zusätzlich eine Angabe, ob dort die Zelle aufgedeckt oder eine Bombe markiert werden soll. Sind die Angaben ungültig (und nicht z.B. eine zu große/kleine Zahl oder ein zu langer String), erscheint eine passende Fehlermeldung und die Eingabe aller drei Werte muß wiederholt werden. Nach einer erfolgreichen Eingabe wird das aktualisierte Spielfeld erneut ausgegeben. Das Spiel endet, wenn alle Zellen außer denen mit Bomben aufgedeckt wurden, wenn der Spieler eine Bombe "aufgedeckt" hat, wenn er eine Zelle fälschlicherweise als Bombe markiert hat oder wenn bei einer der Eingaben mit x/X das Spiel abgebrochen wurde.

Anforderungen
Zunächst sind verschiedene Konstanten und Typen anzulegen:
eine Konstante FIELDSIZE für die Spielfeldbreite bzw. -höhe (das Feld ist also immer quadratisch). Initial könnt Ihr diese auf 7 setzen, andere Größen sollen aber auch möglich sein!
einen Teilbereichstyp TSize für die somit definierte Breite bzw. Höhe (von 1 bis zu der Konstanten)
ein Aufzählungstyp TState für die Zustände einer Zelle des Spielfeldes mit den Werten "leer" und "Bombe"
ein 2D-Array TField in Breite und Höhe entsprechend dem Teilbereichstyp mit dem Aufzählungstyp als Basistyp
ein 2D-Array TVisible in Breite und Höhe entsprechend dem Teilbereichstyp mit Boolean als Basistyp
ein weiterer Aufzählungstyp TDir für die 8 Himmelsrichtungen Nord, NordOst, Ost, SüdOst, Süd, SüdWest, West und NordWest (siehe Hilfe)
je eine Konstante OFFSET_X und OFFSET_Y exakt wie hier vorgegeben (siehe Hilfe):
OFFSET_X: array [TDir] of integer = (0, 1, 1, 1, 0, -1, -1, -1);
OFFSET_Y: array [TDir] of integer = (1, 1, 0, -1, -1, -1, 0, 1);
Danach benötigt Ihr natürlich auch Variablen von den angelegten Typen, konkret mindestens eine von jedem Array und zwei vom Teilbereichstypen, um damit durch das Array zu laufen (denkt dran: ein Typ ist nur eine abstrakte Beschreibung einer Datenstruktur, ihm kann nichts zugewiesen werden!) sowie ein paar weitere ob abgebrochen wurde etc.
Die Variablen dürfen dabei nur direkt vor dem Hauptprogramm deklariert werden, globale Variablen (also solche, die über den Funktionen/Prozeduren deklariert werden), sind nicht erlaubt! Typen und Konstanten hingegen dürfen und sollen gerne zu Beginn des Programms angelegt werden, denn ansonsten sind sie ja in den Funktionen/Prozeduren auch nicht benutzbar.

Prüft an dieser Stelle (indem Ihr einen Abnehmer fragt, wenn Ihr selbst nicht sicher seid), daß die Konstanten und Typen wie gefordert angelegt wurden. Ihr erzeugt Euch ansonsten viel unnötigen Aufwand, wenn diese später noch geändert werden müssen!

Das Hauptprogramm steuert den gesamten Ablauf, insb. soll sich dort eine Schleife befinden, die solange läuft, bis alle Zellen ohne Bomben aufgedeckt wurden, der Spieler eine Bombe "aufgedeckt" oder fälschlicherweise eine Zelle als Bombe markiert hat oder das Spiel abgebrochen wurde. Auch die Ausgaben an den Benutzer (s.u.) sollen soweit möglich hier erfolgen.
Alle sinnvoll auszugliedernden Tätigkeiten sollen in Funktionen bzw. Prozeduren ausgelagert werden. Das Hauptprogramm wird dadurch relativ kurz (in der Musterlösung hat es nur ca. 60 Zeilen).

Folgende Funktionen/Prozeduren sind von Euch zu erstellen:
procedure initField(var field : TField; var visible : TVisible);
Initialisiert das Feld leer und das Sichtbarkeitsfeld mit "false". Setzt zusätzlich in (gerundet) 10% aller Zellen eine Bombe. Um hier zufällige Positionen zu bekommen, ist die Funktion random (zusammen mit randomize) hilfreich. Achtet darauf, eine Bombe nicht in einer Zelle zu platzieren, wo vorher schon eine andere Bombe gelegt wurde!

function isValidCoord(x, y : integer) : boolean;
Prüft, ob eine Koordinate gültig ist (also entsprechend des Teilbereichstyps im Feld liegt).

function countBombs(field : TField; x, y : TSize) : byte;
Gibt zurück, wie viele Bomben sich auf den (maximal) 8 Nachbarzellen der übergebenen Koordinate befinden.
Wichtig: Es ist hier NICHT erlaubt, 8x nahezu denselben Quellcode für die 8 Nachbarzellen redundant zu erstellen, sondern es soll eine einheitliche Lösung in EINER Schleife programmiert werden (siehe Hlife).

procedure printField(field : TField; visible : TVisible);
Gibt das Spielfeld in der Konsole aus (siehe Hilfe und nächster Hauptaufzählungspunkt).

function readInput(var x, y : TSize; var cancel, bomb : boolean) : boolean;
Liest vom Benutzer Spalte und Zeile (oder 'x' für Abbruch) ein und validiert diese Eingaben (prüft also, ob es sich um ganze Zahlen aus dem erlaubten Bereich handelt). Danach wird noch eingelesen, ob eine Bombe markiert (mittels b/B) oder aufgedeckt werden soll (leerer String als Eingabe). Auch hier kann mit x/X abgebrochen werden.
In cancel steht danach also, ob der Nutzer mit 'x' abgebrochen hat, die Funktionsrückgabe gibt an, ob x und y eine gültige Koordinate bilden und auch für das Markieren bzw. Aufdecken der Bombe einer der erlaubten Werte eingegeben wurde.
In bomb steht danach, ob der Spieler an der eingegebenen Koordinate eine Bombe markieren möchte (true) oder aufdecken will (false).
In dieser Funktion soll keine Schleife verwendet werden. Gibt der Nutzer etwas "falsches" ein, wodurch eine erneute Eingabe erforderlich ist, dann soll diese Wiederholung (sprich: Schleife mit erneutem Aufruf der readInput) aus dem Hauptprogramm kommen.

function isFieldSolved(field : TField; visible : TVisible) : boolean;
Prüft, ob das gesamte Spielfeld mit Ausnahme der Bomben aufgedeckt ist.
Die Ausgabe soll jeweils in der oberen linken Ecke der Konsole erfolgen, so daß sich das Feld quasi immer wieder selbst überschreibt (siehe Hilfe).
Noch nicht aufgedeckte Zellen sollen als hellgrauer ▓ ausgegeben werden (Ihr könnt das Zeichen direkt von hier in den Quellcode kopieren).
Aufgedeckte Zellen bekommen entweder ein Leerzeichen (falls in keiner der Nachbarzellen eine Bombe ist), eine farbige Zahl von 1 bis 8 (wenn mindestens eine Nachbarzelle eine Bombe beinhaltet) oder das Zeichen ð für eine (markierte) Bombe.
Zum Setzen der Textfarbe verwendet bitte die Prozedur setTextColor aus der Hilfe. Die benötigten Farbwerte sind 7 (hellgrau für verdeckte Zellen), 15 (weiß für die Bombe), 9/2/12/1/4/3/15/15 (für die Zahlen von 1 bis 8).
Deckt der Nutzer eine Zelle auf, die quasi eine 0 beinhaltet (bei der sich also auf keiner der 8 Nachbarzellen eine Bombe befindet), dann sollen vom Programm die 8 Nachbarzellen (soweit vorhanden und nicht außerhalb des Spielfeldes) direkt mit aufgedeckt werden. Dieses "automatische Aufdecken" soll allerdings NICHT rekursiv für die dadurch aufgedeckten Zellen wiederholt werden.
Wichtig: Es ist hier NICHT erlaubt, 8x nahezu denselben Quellcode für die 8 Nachbarzellen redundant zu erstellen, sondern es soll eine einheitliche Lösung in EINER Schleife programmiert werden (siehe Hlife).
Alle Textausgaben zur Information der Spieler wie "Ungültige Angabe" oder "Spiel abgebrochen" sollen unter dem Spielfeld ausgegeben werden. Versucht, den Spieler mit möglichst detaillierten (Fehler-)Meldungen gut durch das Spiel zu führen.
Das Programm darf keinesfalls abstürzen, egal, was der Benutzer eingibt. Auch bei Buchstaben/Strings, zu großen/kleinen Zahlen oder dem leeren String als Angabe für die Spalte oder Zeile muß das Programm also z.B. (nach Ausgabe einer Fehlermeldung) normal weiterlaufen!
Hier hilft Euch die vordefinierte Prozedur val weiter (siehe Wichtige Prozeduren und Funktionen).
Achtet besonders bei dieser Aufgabe auf korrekt gesetzte Compilerschalter ($R+ für Bereichsüberprüfungen), denn vor allem beim countBombs läuft man sonst schnell mal aus dem Feld hinaus! Denkt weiterhin daran, daß keine Techniken eingesetzt werden dürfen, die in der Übung noch nicht behandelt wurden!
Außerdem soll an allen sinnvollen Stellen der Teilbereichstyp benutzt werden (und eben z.B. nicht eine for-Schleife von 1 bis SIZE). Dazu sind low und high zu benutzen. Das Programm muß also auch dann noch richtig arbeiten, wenn die Konstante verändert wird!



Ich bin jetzt bei der Procedure initField stehen geblieben da ich nicht verstehe wie ich randomize mit einem zweidimensionalen Array nutzen kann.

Außerdem wird auch nicht das verdeckte Spielfeld ausgegeben wenn ich dies versuche, sondern eine Fehlermeldung bei der Bereichsprüfung.

Delphi-Quellcode:
program Uebung_7;

{$APPTYPE CONSOLE}

{$R+,Q+,X-}

uses
  System.SysUtils, Windows;

const
  FIELDSIZE : Byte = 7;

type
  TSize = 1..7;
  TSTATE = (leer, Bombe);
  TDIR = (Nord, NordOst, Ost, SüdOst, Süd, SüdWest, West, NordWest);
  TFIELD = array[TSIZE, TSIZE] of TSTATE;
  TVISIBLE = array[TSIZE, TSIZE] of Boolean;

const
  OFFSET_X : array[TDIR] of integer = (0,1,1,1,0,-1,-1,-1);
  OFFSET_Y : array[TDIR] of integer = (1,1,0,-1,-1,-1,0,1);

//Setzt die Ausgabeposition der Konsole auf die angegebene Koordinate.
//@param
//x,y - zu setzende Position in der Konsole an 0/0 = oben links
procedure setConsolePosition(x,y : byte);
var
  coord : _COORD;
  begin
    coord.X := x;
    coord.Y := y;
    if SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord) then;
  end;

//Setzt die Textfarbe der Konsole
//@param
//color - zu setzender Farbwert
procedure setTextColor(color : word);
begin
  if SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color) then
end;

//Initialisiert das Feld leer und das Sichbarkeitsfeld mit 'false'
//Setzt in gerundet 10% aller Zellen eine Bombe
//@param
//field - Feld, welches initialisiert wird
//visible - zu setzendes Sichtbarkeitsfeld
procedure initField(var field : TFIELD ; var visible : TVISIBLE);
var x, y, i, r, s, run : integer;
begin
   for x := 1 to FIELDSIZE do
   begin
    for y := 1 to FIELDSIZE do
    begin
      visible[x, y] := FALSE;
      field[x,y] := leer;
    end;
   end;
   r := (FIELDSIZE * FIELDSIZE) div 10;
   s := (FIELDSIZE * FIELDSIZE) mod 10;
   if s >= 5 then inc(r);
   //Bomben platzieren
   for run := 1 to r do
   begin
    (* randomize;
     i := random(field[x, y]); *)
     //TODO
   end;
 end;

//Prüft, ob eine Koordinate gültig ist
//@param
// x,y - zu überprüfende Koordinatenwerte
//@out
//Überprüfung ob Koordinate im Bereich des Spielfeldes liegt
//@return
// true, wenn Koordinaten gültig sind
function isValidCoord(x,y : integer): boolean;
begin
  if ((x <= FIELDSIZE) and (x >= 1)) then
  if ((y <= FIELDSIZE) and (y >= 1)) then
  isValidCoord := TRUE
  else isValidCoord := FALSE;
end;

//Zeigt an, wie viele Bomben sich auf den Nachbarzellen, der übergebenen
//Koordinate befinden
//@param
//field - Spielfeld, welches geprüft wird
//x,y - Koordinaten
//@out
//
//@return
// byte-Wert, wie viele Bomben in den Nachbarzellen existieren



//Textausgabe des Spielfeldes in der Konsole
//@param
//field - Spielfeld, welches ausgegeben werden soll
//visible - augedeckte Zellen
procedure printField(field : TFIELD ; visible : TVISIBLE);
var
  x, y : byte;
begin
  x := 1;
  y := 1;
  repeat
  repeat
    if visible[x,y] = FALSE then
    write('&#9619;');
    inc(x);
  until x = FIELDSIZE + 1 ;
  writeln;
  if visible[x,y] = FALSE then
  write('&#9619;');
  inc(y);
  until y = FIELDSIZE +1;
end;

var
  field :TFIELD;
  visible : TVISIBLE;
begin
  initField(field,visible);
  printField(field,visible);
  readln;
end.

Bin über jede Hilfe sehr Dankbar :)

Sinspin 10. Jun 2021 13:50

AW: zweidimensionale Arrays
 
Randomize schon garnicht. Das wird nur einmal außerhalb der Schleife aufgerufen.
Und Random kannst du auch mit einem 10-dimensionalen Array verwenden.

Schau nochmal in die Hilfe was Random macht.

Blup 10. Jun 2021 16:23

AW: zweidimensionale Arrays
 
Eigentlich ist die Projektbeschreibung schon zu detailiert.
Delphi-Quellcode:
   //Bomben platzieren
   while r > 0 do
   begin
     x := Random(FIELDSIZE) + 1; // Random liefert einen Wert 0..(FIELDSIZE - 1)
     y := Random(FIELDSIZE) + 1;
     if field[x, y] = leer then
     begin
       field[x, y] := Bombe;
       Dec(r);
     end;
   end;
Delphi-Quellcode:
procedure printField(field : TFIELD; visible : TVISIBLE);
var
  x, y : byte;
  s: string;
  n: Integer;
begin
  for x := 1 to FIELDSIZE do
  begin
    for y := 1 to FIELDSIZE do
    begin
      if not visible[x, y] then
        s := '&#9619;'
      else if field[x, y] = Bombe then
        s := 'ð'
      else
      begin
        n := countBombs(field, x, y);
        if n = 0 then
          s := ' '
        else
          s := IntToStr(n);
      end;
      write(s);
    end;
    writeln;
  end;
end;
Natürlich sind da noch weitere Anforderungen in der Projektbeschreibung, die zu berücksichtigen sind.

Mo53 10. Jun 2021 16:25

AW: zweidimensionale Arrays
 
Das habe ich jetzt nicht ganz verstanden ?
Muss randomize;
random(...

nicht immer vor Random stehen ?

Sinspin 10. Jun 2021 17:13

AW: zweidimensionale Arrays
 
Eigentlich brauchst Du überhaupt kein Randomize. Es nimmt dir nur arbeit ab.
Randomize legt einen Wert für die globale Variable RandSeed fest. Der Wert wird on der Funktion Random verwendet um Zufallszahlen zu erzeugen.
Willst du immer die gleichen Zufallszahlen, dann legst Du beim Programmstart RandSeed selber fest. Ansonsten rufst Du einmal beim Programmstart Randomize auf.

Michael II 10. Jun 2021 17:18

AW: zweidimensionale Arrays
 
Delphi erzeugt ausgehend von einem Zustand (integer Startwert) Pseudozufallszahlen nach einem festen Muster.

Mit randomize; erzeugst du einen pseudozufälligen Zustand/Startwert.
(Alternativ kannst du mit randseed := .. einen Startwert setzen.)

Mit random(x) kannst du danach Pseudozufallszahlen im Intervall [0..x-1] erzeugen lassen.

Für deine Anwendung reicht es, wenn du genau einmal - zum Beispiel beim Start des Programms randomize; aufrufst. (...oder wenn du willst immer beim Start eines neuen Spiels.)
___________________________________________

Tipp: Anstatt mit randomize; einen pseudozufälligen Zustand/Startwert zu setzen, kannst du mit randseed einen Wert setzen:

Wenn du zum Beispiel randseed := 190391 wählst und danach mittels random(10) Zufallszahlen im Bereich 0..9 erzeugen lässt, dann erzeugt dein Programm auf jedem Computer immer die gleiche Folge von Zufallszahlen 4 4 5 3 4 6 7 5 9 3 2...

Angewendet auf (d)ein Spiel: Du kannst beim Spielstart die Spielerin fragen, welche Spielnummer (sn) sie gerne spielen möchte. - Anstatt mit randomize einen zufälligen Startwert zu erzeugen setzt du in deinem Programm randseed := sn;. Die gleiche Nummer führt dann überall immer zum gleichen Spielfeld.

Das kann spannend sein, wenn Xaver meint, Spiel 2901 sei echt schwer und andere einladen will, es auch zu versuchen.
___________________________________________

himitsu 10. Jun 2021 18:40

AW: zweidimensionale Arrays
 
Randomize wird einmal aufgerufen, um den PseudoZufallsgenerator zu initialisieren.

In einer ConsolenAnwendung ruft man das selber auf, zu Beginn.
In der VCL kann es inzwischen weggelassen werden, da die VCL es initialisiert. (k.A. warum man das in der VCL macht, anstatt einheitlich für Alles in der RTL ... und man kann es immernoch selber machen, wenn man unbedingt will)

Vor jedem Random/RandomRange ein Randomize funktioniert "inzwischen", aber
* vor Jahren wurde noch GetTickCount zur Initialisierung verwendet, womit mehrere Randomize in kurzer Zeit mit dem selben Wert initialisierten
* und auch wenn es jetzt "scheinbar" möglich ist, hat man hier dennoch das Problemchen, dass die generierten Zufallszahlen wahrscheinlich nicht gleichverteilt sind, womit es "ungünstige" Häufungen geben kann

Mo53 10. Jun 2021 19:04

AW: zweidimensionale Arrays
 
Alles Klar, Vielen Dank für die umfangreichen Erklärungen:thumb:

Mo53 10. Jun 2021 21:35

AW: zweidimensionale Arrays
 
Ich hab hier noch ein Problem mit der Funktion readInput.
Und zwar bleibt readInput immer false auch wenn gueltig := true oder cancel := true ist

Delphi-Quellcode:
{$APPTYPE CONSOLE}
{$R+,Q+,X-}

uses
  System.SysUtils,
  Windows;

const
  FIELDSIZE: Byte = 7;

type
  TSize = 1 .. 7;
  TSTATE = (leer, Bombe);
  TDIR = (Nord, NordOst, Ost, SüdOst, Süd, SüdWest, West, NordWest);
  TFIELD = array [TSize, TSize] of TSTATE;
  TVISIBLE = array [TSize, TSize] of Boolean;

const
  OFFSET_X: array [TDIR] of integer = (0, 1, 1, 1, 0, -1, -1, -1);
  OFFSET_Y: array [TDIR] of integer = (1, 1, 0, -1, -1, -1, 0, 1);

  // Setzt die Ausgabeposition der Konsole auf die angegebene Koordinate.
  // @param
  // x,y - zu setzende Position in der Konsole an 0/0 = oben links
procedure setConsolePosition(x, y: Byte);
var
  coord: _COORD;
begin
  coord.x := x;
  coord.y := y;
  if SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord) then;
end;

// Setzt die Textfarbe der Konsole
// @param
// color - zu setzender Farbwert
procedure setTextColor(color: word);
begin
  if SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color) then
end;

// Initialisiert das Feld leer und das Sichbarkeitsfeld mit 'false'
// Setzt in gerundet 10% aller Zellen eine Bombe
// @param
// field - Feld, welches initialisiert wird
// visible - zu setzendes Sichtbarkeitsfeld
procedure initField(var field: TFIELD; var visible: TVISIBLE);
var
  x, y, r, s: integer;
begin
  for x := 1 to FIELDSIZE do
  begin
    for y := 1 to FIELDSIZE do
    begin
      visible[x, y] := FALSE;
      field[x, y] := leer;
    end;
  end;
  r := (FIELDSIZE * FIELDSIZE) div 10;
  s := (FIELDSIZE * FIELDSIZE) mod 10;
  if s >= 5 then
    inc(r);
  // Bomben platzieren
  randomize;
  while r > 0 do
  begin
    x := Random(FIELDSIZE) + 1; // Random liefert einen Wert 0..(FIELDSIZE - 1)
    y := Random(FIELDSIZE) + 1;
    if field[x, y] = leer then
    begin
      field[x, y] := Bombe;
      Dec(r);
    end;
  end;
end;

// Prüft, ob eine Koordinate gültig ist
// @param
// x,y - zu überprüfende Koordinatenwerte
// @out
// Überprüfung ob Koordinate im Bereich des Spielfeldes liegt
// @return
// true, wenn Koordinaten gültig sind
function isValidCoord(x, y: integer): Boolean;
begin
  if ((x <= FIELDSIZE) and (x >= 1)) then
    if ((y <= FIELDSIZE) and (y >= 1)) then
      isValidCoord := TRUE
    else
      isValidCoord := FALSE;
end;

// Zeigt an, wie viele Bomben sich auf den Nachbarzellen, der übergebenen
// Koordinate befinden
// @param
// field - Spielfeld, welches geprüft wird
// x,y - Koordinaten
// @out
// Bestimmung der Nachbarzellen
// @return
// byte-Wert, wie viele Bomben in den Nachbarzellen existieren
function countBombs(field: TFIELD; x, y: TSize): Byte;
var
  dir: TDIR;
  xNachbar, yNachbar: integer;
  n: Byte;
begin
  n := 0;
  for dir := low(TDIR) to high(TDIR) do
  begin
    xNachbar := x + OFFSET_X[dir];
    yNachbar := y + OFFSET_Y[dir];
    if field[x, y] = Bombe then
      inc(n);
  end;
  countBombs := n;
end;

// Textausgabe des Spielfeldes in der Konsole
// @param
// field - Spielfeld, welches ausgegeben werden soll
// visible - augedeckte Zellen
procedure printField(field: TFIELD; visible: TVISIBLE);
var
  x, y: Byte;
  s: string;
  n: integer;
begin
  for x := 1 to FIELDSIZE do
  begin
    for y := 1 to FIELDSIZE do
    begin
      if not visible[x, y] then
        s := '&#9619;'
      else if field[x, y] = Bombe then
        s := 'ð'
      else
      begin
        n := countBombs(field, x, y);
        if n = 0 then
          s := ' '
        else
          s := IntToStr(n);
      end;
      write(s, ' ');
    end;
    writeln;
  end;
end;

// liest vom Benutzer Spalte und Zeile ein und prüft diese. Außerdem wird der
// Benutzer gefragt ob die gewählte Zelle aufgedeckt oder als Bombe markiert
// oder das Programm mit der Eingabe von x beendet werden soll
// @param
// x,y - x- und y-Koordinate des Spielfeldes
// cancel - soll das Spiel verlassen werden?
// bomb - soll eine Bombe markiert werden?
// @out
//
// @return
function readInput(var x, y: TSize; var cancel, bomb: Boolean): Boolean;
var
  eingabe: char;
  gueltig: Boolean;
begin
  eingabe := upcase(eingabe);
  gueltig := FALSE;
  case eingabe of
    'X':
      cancel := TRUE;
    '1' .. '7':
      gueltig := TRUE;
  end;

  case eingabe of
    'X':
      cancel := TRUE;
    'B':
      bomb := TRUE;
  end;
  readInput := gueltig or cancel;
end;

// Prüft, ob das gesamte Spielfeld mit Ausnahme der Bomben aufgedeckt ist
// @param
// field - Spielfeld, in dem geprüft werden soll
// visible -Sichtbarkeit der Zellen
// @out
//
// @return
// true, wenn alle Zellen außer die Bomben aufgedeckt sind
(* function isFieldSolved(field : TFIELD; visible :TVISIBLE) : boolean;
  begin
  while visible or (field = Bombe) do

  end; *)

var
  field: TFIELD;
  visible: TVISIBLE;
  cancel, bomb: Boolean;
  x, y: TSize;

begin
  initField(field, visible);
  printField(field, visible);
  writeln('Bitte eine Spalte von 1 bis 7 eingeben oder ''X'' für Abbruch: ');
  readln(x);
  if readInput(x, y, cancel, bomb) then
  begin
    writeln('Bitte eine Zeile von 1 bis 7 eingeben oder ''X'' für Abbruch: ');
    readln(y);
    if readInput(x, y, cancel, bomb) then
    begin
      writeln('Bitte ein ''B'' eingeben, wenn dort eine Bombe markiert werden soll, '
        + 'leer lassen zum aufdecken oder ''X'' für Abbruch: ');
    end;
  end;

  readln;

end.

Michael II 11. Jun 2021 00:46

AW: zweidimensionale Arrays
 
Ich frage mich, wieso du das hier kompilieren kannst: eingabe := upcase(eingabe); ? Mein Delphi könnte das nicht ;-).

Wenn du in #1 deine Beschreibung von function readInput(var x, y: TSize; var cancel, bomb: Boolean): Boolean; liest, dann könnte man vermuten, dass die Benutzereingaben Spalte, Zeile etc. direkt IN der Funktion eingelesen werden sollten und nicht im Hauptprogramm. (Darauf deutet auch hin, dass die Argumente als var Parameter deklariert sind.).

himitsu 11. Jun 2021 12:31

AW: zweidimensionale Arrays
 
Debuggen und schauen was passiert?


@Michael: Das ist der Unterschied(e) zwischen Delphi-Referenz durchsuchenUpCase (ASCII-Char), Delphi-Referenz durchsuchenUpperCase (ASCII) und Delphi-Referenz durchsuchenAnsiUpperCase (ANSI/Unicode) :stupid:

Michael II 11. Jun 2021 13:47

AW: zweidimensionale Arrays
 
@Michael: Das ist der Unterschied(e) zwischen Delphi-Referenz durchsuchenUpCase (ASCII-Char), Delphi-Referenz durchsuchenUpperCase (ASCII) und Delphi-Referenz durchsuchenAnsiUpperCase (ANSI/Unicode) :stupid:[/QUOTE]

Danke ;-) himitsu. Dann würde mich spontan auf den Rest dieser Zeile - zum Beispiel den Wert von eingabe - konzentrieren.
mo du schreibst gueltig oder cancel seien true. Woher hast du diese Werte? Du weisst, wie du debuggen kannst? Du kannst beim Debuggen Programmzeile für Programmzeile beobachten, was passiert und welche Werte in deinen Variablen gespeichert sind.

DeddyH 11. Jun 2021 14:09

AW: zweidimensionale Arrays
 
Zitat:

Delphi-Quellcode:
var
  eingabe: char;
  gueltig: Boolean;
begin
  eingabe := upcase(eingabe);

Welchen Wert hat denn eingabe zu diesem Zeitpunkt? Das weißt Du nicht? Gut, dann sind wir schon 2 :mrgreen:

P.S.: Eigentlich müsste der Compiler hier eine Warnung ausgeben.

Mo53 11. Jun 2021 20:07

AW: zweidimensionale Arrays
 
Ich habe ein kleines Problem und zwar das falls eine Bombe eingetragen werden soll einfach nichts geschieht.
Sieht vielleicht jemand von euch wo der Fehler ist, der Debugger hilft mir da auch nicht weiter.
:drunken:


Delphi-Quellcode:

{$APPTYPE CONSOLE}

{$R+,Q+,X-}

uses
  System.SysUtils,
  Windows;

const
  FIELDSIZE : Byte = 7;

type
  TSize = 1..7;
  TSTATE = (leer, Bombe);
  TDIR = (Nord, NordOst, Ost, SüdOst, Süd, SüdWest, West, NordWest);
  TFIELD = array[TSIZE, TSIZE] of TSTATE;
  TVISIBLE = array[TSIZE, TSIZE] of Boolean;

const
  OFFSET_X : array[TDIR] of integer = (0,1,1,1,0,-1,-1,-1);
  OFFSET_Y : array[TDIR] of integer = (1,1,0,-1,-1,-1,0,1);

//Setzt die Ausgabeposition der Konsole auf die angegebene Koordinate.
//@param
//x,y - zu setzende Position in der Konsole an 0/0 = oben links
procedure setConsolePosition(x,y : byte);
var
  coord : _COORD;
  begin
    coord.X := x;
    coord.Y := y;
    if SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord) then;
  end;

//Setzt die Textfarbe der Konsole
//@param
//color - zu setzender Farbwert
procedure setTextColor(color : word);
begin
  if SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color) then
end;

//Initialisiert das Feld leer und das Sichbarkeitsfeld mit 'false'
//Setzt in gerundet 10% aller Zellen eine Bombe
//@param
//field - Feld, welches initialisiert wird
//visible - zu setzendes Sichtbarkeitsfeld
procedure initField(var field : TFIELD ; var visible : TVISIBLE);
var x, y, r, s : integer;
begin
   for x := 1 to FIELDSIZE do
   begin
    for y := 1 to FIELDSIZE do
    begin
      visible[x, y] := FALSE;
      field[x,y] := leer;
    end;
   end;
   r := (FIELDSIZE * FIELDSIZE) div 10;
   s := (FIELDSIZE * FIELDSIZE) mod 10;
   if s >= 5 then
     inc(r);
   // Bomben platzieren
   randomize;
   while r > 0 do
   begin
     x := Random(FIELDSIZE) + 1; // Random liefert einen Wert 0..(FIELDSIZE - 1)
     y := Random(FIELDSIZE) + 1;
     if field[x, y] = leer then
     begin
       field[x, y] := Bombe;
       Dec(r);
     end;
   end;
 end;

 //Prüft, ob eine Koordinate gültig ist
//@param
// x,y - zu überprüfende Koordinatenwerte
//@out
//Überprüfung ob Koordinate im Bereich des Spielfeldes liegt
//@return
// true, wenn Koordinaten gültig sind
function isValidCoord(x,y : integer): boolean;
begin
  if ((x <= FIELDSIZE) and (x >= 1)) then
  if ((y <= FIELDSIZE) and (y >= 1)) then
  isValidCoord := TRUE
  else isValidCoord := FALSE;
end;

//Zeigt an, wie viele Bomben sich auf den Nachbarzellen, der übergebenen
//Koordinate befinden
//@param
//field - Spielfeld, welches geprüft wird
//x,y - Koordinaten
//@out
//Bestimmung der Nachbarzellen
//@return
// byte-Wert, wie viele Bomben in den Nachbarzellen existieren
function countBombs(field : TFIELD; x,y : TSIZE) : byte;
var
  dir : TDIR;
  xNachbar, yNachbar : integer;
  n : byte;
begin
  n := 0;
  for dir := low(TDir) to high(TDir) do
    begin
      xNachbar := x + OFFSET_X[dir];
      yNachbar := y + OFFSET_Y[dir];
      if field[x,y] = Bombe then inc(n);
    end;
  countBombs := n;
end;

//Textausgabe des Spielfeldes in der Konsole
//@param
//field - Spielfeld, welches ausgegeben werden soll
//visible - augedeckte Zellen
procedure printField(field : TFIELD; visible : TVISIBLE);
var
  x, y : byte;
  s: string;
  n: Integer;
begin
  for x := 1 to FIELDSIZE do
  begin
    for y := 1 to FIELDSIZE do
    begin
      if not visible[x, y] then
        s := '&#9619;'
      else if visible[x,y] and (field[x, y] = Bombe) then
        s := 'ð'
      else
      begin
        n := countBombs(field, x, y);
        if n = 0 then
          s := ' '
        else
          s := IntToStr(n);
      end;
      write(s,' ');
    end;
    writeln;
  end;
end;

//liest vom Benutzer Spalte und Zeile ein und prüft diese. Außerdem wird der
//Benutzer gefragt ob die gewählte Zelle aufgedeckt oder als Bombe markiert
//oder das Programm mit der Eingabe von x beendet werden soll
//@param
//x,y - x- und y-Koordinate des Spielfeldes
//cancel - soll das Spiel verlassen werden?
//bomb - soll eine Bombe markiert werden?
//@out
//
//@return
function readInput(var x, y: TSize; var cancel, bomb: Boolean): Boolean;
var
  gueltig: Boolean;
  eingabeX, eingabeY, eingabeZ: Char;
  field : TFIELD;
  visible : TVISIBLE;
begin
  eingabeX := upcase(eingabeX);
  gueltig := FALSE;
  cancel := FALSE;
  readInput := gueltig or cancel;
  writeln('Bitte eine Spalte von 1 bis 7 eingeben oder ''X'' für Abbruch: ');
  readln(eingabeX);
  case eingabeX of
    'X': cancel := TRUE;
    '1'..'7': begin
                  gueltig := TRUE;
                  x := StrToInt(eingabeX);
              end;

  else readInput := FALSE;
  end;

  if gueltig then
  begin
    writeln('Bitte eine Zeile von 1 bis 7 eingeben oder ''X'' für Abbruch: ');
    readln(eingabeY);
    case eingabeY of
      'X': cancel := TRUE;
      '1' .. '7': begin
                    gueltig := TRUE;
                    y := StrToInt(eingabeY);
                  end
    else readInput := FALSE;
    end;
  end;

  if isValidCoord(x,y) then
  begin
    writeln('Bitte ein ''B'' eingeben, wenn dort eine Bombe markiert werden '+
    'soll, leer lassen zum Aufdecken oder ''X'' für Abbruch: ');
    readln(eingabeZ);
    case eingabeZ of
    'B' : field[x,y] := Bombe;
    'X' : cancel := TRUE;
    else readInput := FALSE;
    end;
    visible[x,y] := TRUE;
  end;
end;




// Prüft, ob das gesamte Spielfeld mit Ausnahme der Bomben aufgedeckt ist
// @param
// field - Spielfeld, in dem geprüft werden soll
// visible -Sichtbarkeit der Zellen
// @out
//
// @return
// true, wenn alle Zellen außer die Bomben aufgedeckt sind
(* function isFieldSolved(field : TFIELD; visible :TVISIBLE) : boolean;
  begin
  while visible or (field = Bombe) do



  end; *)



var
  field: TFIELD;
  visible: TVISIBLE;
  cancel, bomb: Boolean;
  x, y: TSize;
  eingabe: char;
  eingabeX: integer;
  eingabeY: integer;



begin
  initField(field, visible);
  printField(field, visible);
  while readInput(x,y,cancel, bomb) do
  begin
    writeln('haha');
    printField(field,visible);
  end;
 readln;
end.

Michael II 11. Jun 2021 20:49

AW: zweidimensionale Arrays
 
Zitat:

Zitat von Mo53 (Beitrag 1491018)
Ich habe ein kleines Problem und zwar das falls eine Bombe eingetragen werden soll einfach nichts geschieht.
Sieht vielleicht jemand von euch wo der Fehler ist, der Debugger hilft mir da auch nicht weiter.

Der Debugger hilft da schon sehr weiter.
Setze einen Breakpoint am Ende von readInput.
Starte das Programm. Wähle Spalte und Zeile und setze mit B eine Bombe.
Der Debugger hält nun am Ende von readInput an.
Jetzt kannst du weiter steppen mit Funktionstaste F8 und siehst, was passiert. (Deine Funktion readInput gibt offenbar false zurück.)

Nebenbei:
eingabeX := upcase(eingabeX);
macht dort wo's steht immer noch gleich wenig Sinn; siehe #10 und #13. Tipp: Beim Kompilieren werden dir Warnungen angezeigt. Lies diese... Bei dieser Zeile steht zum Beispiel:
W1036 Variable 'eingabeX' ist möglicherweise nicht initialisiert worden.
Und das stimmt auch ;-): eingabeX hat in dieser Zeile noch keinen definierten Wert. Erst NACH dem Einlesen mittels readln(eingabeX); macht upcase() Sinn. Nach readln(eingabeY) und auch bei ...z solltest du wieder upcase() nutzen oder aber so:

TIPP: Anstatt upcase() zu nutzen kannst du im case 'X' und 'x' angeben:

Delphi-Quellcode:
  case eingabeX of
    'X', 'x' : cancel := TRUE;
    '1'..'7': begin
                  gueltig := TRUE;
                  x := StrToInt(eingabeX);
              end;

Soweit ich mich erinnere soll gemäss Aufgabenstellung dein Spielfeld auch grösser oder kleiner als 7x7 sein dürfen. Wen dem so ist: In deinem Programm sollte nur hier
Delphi-Quellcode:
const
  FIELDSIZE : Byte = 7;
die Zahl 7 stehen. Suche via IDE nach 7 und ersetze... - Was passiert bei deinen case Abfragen, wenn der Wert FELDSIZE zweistellig ist? (In der Aufgabenstellung wird zur Validierung der Eingabe val(..) empfohlen.)



Viel Spass.

Mo53 12. Jun 2021 10:33

AW: zweidimensionale Arrays
 
Da kommt durchgehend nicht verfügbarer Wert für readInput raus :pale:

Michael II 12. Jun 2021 12:44

AW: zweidimensionale Arrays
 
Zitat:

Zitat von Mo53 (Beitrag 1491023)
Da kommt durchgehend nicht verfügbarer Wert für readInput raus :pale:

Ja ich hab's gesehen. Wenn ich readInput auswerten will, dann will Delphi den Funktionswert berechnen und erwartet deshalb Parameter... (wenn ich mich nicht voll täusche, dann war das bei viel früheren Versionen nicht so - vielleicht ist auch irgend eine "doofe" Einstellung der Grund...).

Aber: Den Funktionswert solltest du auch in "Result" finden.


Tipp: Du kannst den Funktionswert auch immer als Result := <wert> zurückgeben. Also so:

Delphi-Quellcode:
function readInput(var x, y: TSize; var cancel, bomb: Boolean): Boolean;
...
...
   Result := ...
end;

Mo53 12. Jun 2021 14:33

AW: zweidimensionale Arrays
 
Ich hab das mit Result nie wirklich verstanden, ist das eine Funktion ?
Bei mir sagt er dann undeklarierter Bezeichner "Result".

Delphi.Narium 12. Jun 2021 14:42

AW: zweidimensionale Arrays
 
Beispiel:

Statt
Delphi-Quellcode:
function isValidCoord(x,y : integer): boolean;
begin
  if ((x <= FIELDSIZE) and (x >= 1)) then
  if ((y <= FIELDSIZE) and (y >= 1)) then
  isValidCoord := TRUE
  else isValidCoord := FALSE;
end;
einfach
Delphi-Quellcode:
function isValidCoord(x,y : integer): boolean;
begin
  if ((x <= FIELDSIZE) and (x >= 1)) then
  if ((y <= FIELDSIZE) and (y >= 1)) then
  Result := TRUE
  else Result := FALSE;
end;
Für die Rückgabe eines Funktionsergebnisses kann man dem Funktionsnamen einen Wert zuweisen oder eben statt des Funktionsnamens Result schreiben.

Result ist quasi ein Synonym für den Funktionsnamen.

Delphi-Quellcode:
Funktionsname := Wert;
entspricht
Delphi-Quellcode:
Result := Wert;

Michael II 12. Jun 2021 17:13

AW: zweidimensionale Arrays
 
Zitat:

Zitat von Mo53 (Beitrag 1491030)
Bei mir sagt er dann undeklarierter Bezeichner "Result".

Hoi mo

genau... wenn du den Wert von Result abfragst, dann zeigt dein Delphi an
E2003 Undeklarierter Bezeichner: 'result'

Das liegt am Compilerschalter $X- ganz oben in deinem Code.
Nimm diesen Schalter raus. Wahrscheinlich wurde diese Übung im 7. Jahrhundert geschrieben und von Generation zu Generation weitergegeben.

Zitat:

Note: The $X directive is provided for backward compatibility. You should not use the {$X-} mode when writing Delphi applications.
Hier findest du Infos dazu:
http://docwiki.embarcadero.com/RADSt...yntax_(Delphi)

Kompiliere dein Programm neu. Setze wieder einen Haltepunkt in readInput. Jetzt wird dir im Fenster "Lokale Variablen" (Menu Ansicht > Debug Fenster > Lokale Variablen) neu auch Result (mit Wert <Wert>) angezeigt. Es ist egal, ob du in deinem Code <Wert> über Result:=<Wert> oder readInput:=<Wert> zugewiesen hast.

Andreas13 12. Jun 2021 20:10

AW: zweidimensionale Arrays
 
Zitat:

Zitat von Mo53 (Beitrag 1491030)
Ich hab das mit Result nie wirklich verstanden, ist das eine Funktion ?
Bei mir sagt er dann undeklarierter Bezeichner "Result".

Welche Delphi-Version verwendest Du? Du kannst & solltest diese in Deinem Profil eintragen: Das vermeidet u.U. dumme Fragen von uns... :)
Gruß, Andreas

Mo53 12. Jun 2021 23:18

AW: zweidimensionale Arrays
 
Ich verwende Delphi 10.3

Danke für den Tipp:-D

Mo53 13. Jun 2021 02:07

AW: zweidimensionale Arrays
 
Ich habe das jetzt soweit, jedoch stürzt er ab wenn er eine Bombe findet, sieht jemand von euch warum ?

Delphi-Quellcode:
{$APPTYPE CONSOLE}
{$R+,Q+,X-}

uses
  System.SysUtils,
  Windows;

const
  FIELDSIZE: Byte = 7;

type
  TSize = 1 .. 7;
  TSTATE = (leer, Bombe);
  TDIR = (Nord, NordOst, Ost, SüdOst, Süd, SüdWest, West, NordWest);
  TFIELD = array [TSize, TSize] of TSTATE;
  TVISIBLE = array [TSize, TSize] of Boolean;

const
  OFFSET_X: array [TDIR] of integer = (0, 1, 1, 1, 0, -1, -1, -1);
  OFFSET_Y: array [TDIR] of integer = (1, 1, 0, -1, -1, -1, 0, 1);

  // Setzt die Ausgabeposition der Konsole auf die angegebene Koordinate.
  // @param
  // x,y - zu setzende Position in der Konsole an 0/0 = oben links
procedure setConsolePosition(x, y: Byte);
var
  coord: _COORD;
begin
  coord.x := x;
  coord.y := y;
  if SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord) then;
end;

// Setzt die Textfarbe der Konsole
// @param
// color - zu setzender Farbwert
procedure setTextColor(color: word);
begin
  if SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color) then
end;

// Initialisiert das Feld leer und das Sichbarkeitsfeld mit 'false'
// Setzt in gerundet 10% aller Zellen eine Bombe
// @param
// field - Feld, welches initialisiert wird
// visible - zu setzendes Sichtbarkeitsfeld
procedure initField(var field: TFIELD; var visible: TVISIBLE);
var
  x, y, r, s: integer;
begin
  for x := 1 to FIELDSIZE do
  begin
    for y := 1 to FIELDSIZE do
    begin
      visible[x, y] := FALSE;
      field[x, y] := leer;
    end;
  end;
  r := (FIELDSIZE * FIELDSIZE) div 10;
  s := (FIELDSIZE * FIELDSIZE) mod 10;
  if s >= 5 then
    inc(r);
  // Bomben platzieren
  randomize;
  while r > 0 do
  begin
    x := Random(FIELDSIZE) + 1; // Random liefert einen Wert 0..(FIELDSIZE - 1)
    y := Random(FIELDSIZE) + 1;
    if field[x, y] = leer then
    begin
      field[x, y] := Bombe;
      Dec(r);
    end;
  end;
end;

// Prüft, ob eine Koordinate gültig ist
// @param
// x,y - zu überprüfende Koordinatenwerte
// @out
// Überprüfung ob Koordinate im Bereich des Spielfeldes liegt
// @return
// true, wenn Koordinaten gültig sind
function isValidCoord(x, y: integer): Boolean;
begin
  if ((x <= FIELDSIZE) and (x >= 1)) then
    if ((y <= FIELDSIZE) and (y >= 1)) then
      isValidCoord := TRUE
    else
      isValidCoord := FALSE;
end;

// Zeigt an, wie viele Bomben sich auf den Nachbarzellen, der übergebenen
// Koordinate befinden
// @param
// field - Spielfeld, welches geprüft wird
// x,y - Koordinaten
// @out
// Bestimmung der Nachbarzellen
// @return
// byte-Wert, wie viele Bomben in den Nachbarzellen existieren
function countBombs(field: TFIELD; x, y: TSize): Byte;
var
  dir: TDIR;
  xNachbar, yNachbar: integer;
  n: Byte;
begin
  n := 0;
  for dir := low(TDIR) to high(TDIR) do
  begin
    xNachbar := x + OFFSET_X[dir];
    yNachbar := y + OFFSET_Y[dir];
    if field[xNachbar, yNachbar] = Bombe then
      inc(n);
  end;
  countBombs := n;
end;

// Textausgabe des Spielfeldes in der Konsole
// @param
// field - Spielfeld, welches ausgegeben werden soll
// visible - augedeckte Zellen
procedure printField(field: TFIELD; visible: TVISIBLE);
var
  x, y: TSize;
  s: string;
  n, i, j: integer;

begin
  setConsolePosition(0, 0);
  for i := 0 to 10 do
  begin
    for j := 0 to 150 do
    begin
      write(' ');
    end;
    writeln;
  end;

  setConsolePosition(0, 0);
  for x := low(TSize) to high(TSize) do
  begin
    for y := low(TSize) to high(TSize) do
    begin
      if not visible[x, y] then
        s := '&#9619;'
      else if visible[x, y] and (field[x, y] = Bombe) then
        s := 'ð'
      else
      begin
        n := countBombs(field, x, y);
        if n = 0 then
          s := ' '
        else
          s := IntToStr(n);
      end;
      write(s, ' ');
    end;
    writeln;
  end;
end;

// liest vom Benutzer Spalte und Zeile ein und prüft diese. Außerdem wird der
// Benutzer gefragt ob die gewählte Zelle aufgedeckt oder als Bombe markiert
// oder das Programm mit der Eingabe von x beendet werden soll
// @param
// x,y - x- und y-Koordinate des Spielfeldes
// cancel - soll das Spiel verlassen werden?
// bomb - soll eine Bombe markiert werden?
// @out
//
// @return
function readInput(var x, y: TSize; var cancel, bomb: Boolean): Boolean;
var
  gueltig: Boolean;
  eingabeX, eingabeY, eingabeZ: char;
  visible: TVISIBLE;
begin
  gueltig := FALSE;
  cancel := FALSE;
  readInput := gueltig or cancel or bomb;

  writeln;
  writeln('Bitte eine Zeile von 1 bis 7 eingeben oder ''X'' für Abbruch: ');
  readln(eingabeX);
  eingabeX := upcase(eingabeX);
  // repeat
  case eingabeX of
    'X':
      begin
        cancel := TRUE;
        readInput := TRUE;
      end;
    '1' .. '7':
      begin
        gueltig := TRUE;
        x := StrToInt(eingabeX);
      end;

  else
    readInput := FALSE;
  end;
  // until readInput(x,y,cancel,bomb):= TRUE;

  if not cancel and gueltig then
  begin
    gueltig := FALSE;
    writeln('Bitte eine Spalte von 1 bis 7 eingeben oder ''X'' für Abbruch: ');
    readln(eingabeY);
    eingabeY := upcase(eingabeY);
    case eingabeY of
      'X':
        begin
          cancel := TRUE;
          readInput := TRUE;
        end;
      '1' .. '7':
        begin
          gueltig := TRUE;
          y := StrToInt(eingabeY);
        end
    else
      readInput := FALSE;
    end;
  end;

  if not cancel and gueltig and isValidCoord(x, y) then
  begin

    writeln('Bitte ein ''B'' eingeben, wenn dort eine Bombe markiert werden ' +
      'soll, leer lassen zum Aufdecken oder ''X'' für Abbruch: ');
    readln(eingabeZ);
    eingabeZ := upcase(eingabeZ);
    case eingabeZ of
      'B':
        bomb := TRUE;
      'X':
        begin
          cancel := TRUE;
          readInput := TRUE;
        end

    else
      begin
        readInput := FALSE;
        visible[x, y] := TRUE;
      end;

    end;
    visible[x, y] := TRUE;

  end;

end;

// Prüft, ob das gesamte Spielfeld mit Ausnahme der Bomben aufgedeckt ist
// @param
// field - Spielfeld, in dem geprüft werden soll
// visible -Sichtbarkeit der Zellen
// @out
//
// @return
// true, wenn alle Zellen außer die Bomben aufgedeckt sind
function isFieldSolved(field: TFIELD; visible: TVISIBLE): Boolean;
var
  x, y: TSize;
  z, b: integer;
begin
  z := 0;
  b := 0;
  for x := 1 to FIELDSIZE do
    for y := 1 to FIELDSIZE do
    begin
      if visible[x, y] then
        inc(z);
      if field[x, y] = Bombe then
        inc(b);
    end;
  isFieldSolved := z = FIELDSIZE * FIELDSIZE - b;
end;
// -------------------------------------------------------------------------------

var
  field: TFIELD;
  visible: TVISIBLE;
  cancel, bomb: Boolean;
  x, y: TSize;
  dir: TDIR;
  xNachbar, yNachbar: integer;
  gueltig: Boolean;

begin
  initField(field, visible);
  repeat
    while not cancel do
    begin
      repeat
        printField(field, visible);
        cancel := readInput(x, y, cancel, bomb);
      visible[x, y] := TRUE;
      until gueltig = TRUE;

      if cancel then
      begin
        writeln('Ende');
        readln;
      end
      else
        // weitere Zellen werden aufgedeckt, wenn in keiner Nachbarzelle eine Bombe ist
        if countBombs(field, x, y) = 0 then
          for dir := low(TDIR) to high(TDIR) do
          begin
            xNachbar := x + OFFSET_X[dir];
            yNachbar := y + OFFSET_Y[dir];
            visible[xNachbar, yNachbar] := TRUE;
          end;

      // wenn eine Bombe aufgedeckt wird
      if ((field[x, y] = Bombe) and visible[x, y]) then
      begin
        writeln('PENG!!!');
        cancel := TRUE;
      end;

    end;

  until isFieldSolved(field, visible) or cancel;
  writeln('Tschüss! Bis zum nächsten Mal');
  readln;

end.

zeras 13. Jun 2021 06:48

AW: zweidimensionale Arrays
 
Zitat:

Zitat von Mo53 (Beitrag 1491036)
Ich habe das jetzt soweit, jedoch stürzt er ab wenn er eine Bombe findet, sieht jemand von euch warum ?

Dann stelle dich doch mit dem Debugger an den Beginn der Funktion, wo er die Bombe findet und steppe weiter. Dann wirst du die Zeile finden, wo der Crash passiert.
Vielleicht kommst du da selber drauf oder postest hier erneut.

haentschman 13. Jun 2021 09:21

AW: zweidimensionale Arrays
 
Zitat:

Dann stelle dich doch mit dem Debugger an den Beginn der Funktion
Debugger Tutorial: https://www.delphipraxis.net/41047-t...ortgeschr.html
...alles Andere ist geraten. :wink:

Mo53 13. Jun 2021 12:15

AW: zweidimensionale Arrays
 
Zitat:

Zitat von haentschman (Beitrag 1491041)
Zitat:

Dann stelle dich doch mit dem Debugger an den Beginn der Funktion
Debugger Tutorial: https://www.delphipraxis.net/41047-t...ortgeschr.html
...alles Andere ist geraten. :wink:

ist das auch aktuell mit delphi 10.3 ?

haentschman 13. Jun 2021 12:33

AW: zweidimensionale Arrays
 
Zitat:

ist das auch aktuell mit delphi 10.3 ?
Ja. Sinngemäß schon seit D1 (imho). :wink:

Mo53 14. Jun 2021 01:10

AW: zweidimensionale Arrays
 
Mein Programm steht jetzt mittlerweile fast, jedoch habe ich zwei Probleme an denen ich fast verzweifel, zum einen sieht das Programm nach der zweiten Abfrage jedes Zeichen als gültig und deckt sofort auf ohne überhaupt die dritte Abfrage auszuführen, zum anderen markiert das Programm das ausgewählte Feld nicht mit einer Bombe sobald ich auf b drücke, sondern deckt sofort auf. Ich wäre echt über jede Hilfe dankbar.

Mo53 14. Jun 2021 01:12

AW: zweidimensionale Arrays
 
Delphi-Quellcode:
{$APPTYPE CONSOLE}
{$R+,Q+,X-}

uses
  System.SysUtils,
  Windows;

const
  FIELDSIZE: Byte = 7;

type
  TSize = 1 .. 7;
  TSTATE = (leer, Bombe);
  TDIR = (Nord, NordOst, Ost, SüdOst, Süd, SüdWest, West, NordWest);
  TFIELD = array [TSize, TSize] of TSTATE;
  TVISIBLE = array [TSize, TSize] of Boolean;

const
  OFFSET_X: array [TDIR] of integer = (0, 1, 1, 1, 0, -1, -1, -1);
  OFFSET_Y: array [TDIR] of integer = (1, 1, 0, -1, -1, -1, 0, 1);

  // Setzt die Ausgabeposition der Konsole auf die angegebene Koordinate.
  // @param
  // x,y - zu setzende Position in der Konsole an 0/0 = oben links
procedure setConsolePosition(x, y: Byte);
var
  coord: _COORD;
begin
  coord.x := x;
  coord.y := y;
  if SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord) then;
end;

// Setzt die Textfarbe der Konsole
// @param
// color - zu setzender Farbwert
procedure setTextColor(color: word);
begin
  if SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color) then
end;

// Initialisiert das Feld leer und das Sichbarkeitsfeld mit 'false'
// Setzt in gerundet 10% aller Zellen eine Bombe
// @param
// field - Feld, welches initialisiert wird
// visible - zu setzendes Sichtbarkeitsfeld
procedure initField(var field: TFIELD; var visible: TVISIBLE);
var
  x, y, r, s: integer;
begin
  for x := 1 to FIELDSIZE do
  begin
    for y := 1 to FIELDSIZE do
    begin
      visible[x, y] := FALSE;
      field[x, y] := leer;
    end;
  end;
  r := (FIELDSIZE * FIELDSIZE) div 10;
  s := (FIELDSIZE * FIELDSIZE) mod 10;
  if s >= 5 then
    inc(r);
  // Bomben platzieren
  randomize;
  while r > 0 do
  begin
    x := Random(FIELDSIZE) + 1; // Random liefert einen Wert 0..(FIELDSIZE - 1)
    y := Random(FIELDSIZE) + 1;
    if field[x, y] = leer then
    begin
      field[x, y] := Bombe;
      Dec(r);
    end;
  end;
end;

// Prüft, ob eine Koordinate gültig ist
// @param
// x,y - zu überprüfende Koordinatenwerte
// @out
// Überprüfung ob Koordinate im Bereich des Spielfeldes liegt
// @return
// true, wenn Koordinaten gültig sind
function isValidCoord(x, y: integer): Boolean;
begin
  if ((x <= FIELDSIZE) and (x >= 1)) then
    if ((y <= FIELDSIZE) and (y >= 1)) then
      isValidCoord := TRUE
    else
      isValidCoord := FALSE;
end;

// Zeigt an, wie viele Bomben sich auf den Nachbarzellen, der übergebenen
// Koordinate befinden
// @param
// field - Spielfeld, welches geprüft wird
// x,y - Koordinaten
// @out
// Bestimmung der Nachbarzellen
// @return
// byte-Wert, wie viele Bomben in den Nachbarzellen existieren
function countBombs(field: TFIELD; x, y: TSize): Byte;
var
  dir: TDIR;
  xNachbar, yNachbar: integer;
  n: Byte;
begin
  n := 0;
  for dir := low(TDIR) to high(TDIR) do
  begin
    xNachbar := x + OFFSET_X[dir];
    yNachbar := y + OFFSET_Y[dir];
    if ((xNachbar > 0) and (xNachbar < 8) and (yNachbar > 0) and (yNachbar < 8))
    then

      if field[xNachbar, yNachbar] = Bombe then
        inc(n);
  end;
  countBombs := n;
end;

// Textausgabe des Spielfeldes in der Konsole
// @param
// field - Spielfeld, welches ausgegeben werden soll
// visible - augedeckte Zellen
procedure printField(field: TFIELD; visible: TVISIBLE);
var
  x, y: TSize;
  s: string;
  n, i, j: integer;

begin
  setConsolePosition(0, 0);
  for i := 0 to 10 do
  begin
    for j := 0 to 150 do
    begin
      write(' ');
    end;
    writeln;
  end;

  setConsolePosition(0, 0);
  for x := low(TSize) to high(TSize) do
  begin
    for y := low(TSize) to high(TSize) do
    begin
      if not visible[x, y] then
      begin
        setTextColor(7);
        s := '&#9619;'
      end
      else if not visible[x, y] and (field[x, y] = Bombe) then
        s := 'ð'
      else
      begin
        n := countBombs(field, x, y);
        case n of
          0:
            s := ' ';
          1:
            begin
              setTextColor(9);
              s := '1';
            end;
          2:
            begin
              setTextColor(2);
              s := '2';
            end;
          3:
            begin
              setTextColor(12);
              s := '3';
            end;
          4:
            begin
              setTextColor(1);
              s := '4';
            end;
          5:
            begin
              setTextColor(4);
              s := '5';
            end;
          6:
            begin
              setTextColor(3);
              s := '6';
            end;
          7:
            begin
              setTextColor(15);
              s := '7';
            end;
        end;
      end;
      write(s, ' ');
    end;
    writeln;
  end;
end;

// liest vom Benutzer Spalte und Zeile ein und prüft diese. Außerdem wird der
// Benutzer gefragt ob die gewählte Zelle aufgedeckt oder als Bombe markiert
// oder das Programm mit der Eingabe von x beendet werden soll
// @param
// x,y - x- und y-Koordinate des Spielfeldes
// cancel - soll das Spiel verlassen werden?
// bomb - soll eine Bombe markiert werden?
// @out
//
// @return
function readInput(var x, y: TSize; var cancel, bomb: Boolean): Boolean;
var
  gueltig: Boolean;
  eingabeX, eingabeY, eingabeZ: char;
  visible: TVISIBLE;
  field: TFIELD;
begin
  gueltig := FALSE;
  cancel := FALSE;
  readInput := gueltig or cancel or bomb;

  writeln;
  writeln('Bitte eine Zeile von 1 bis 7 eingeben oder ''X'' für Abbruch: ');
  readln(eingabeX);
  eingabeX := upcase(eingabeX);
  case eingabeX of
    'X':
      begin
        cancel := TRUE;
        readInput := TRUE;
      end;
    '1' .. '7':
      begin
        gueltig := TRUE;
        x := StrToInt(eingabeX);
      end;

  else
    readInput := FALSE;
  end;

  if not cancel and gueltig then
  begin
    gueltig := FALSE;
    writeln('Bitte eine Spalte von 1 bis 7 eingeben oder ''X'' für Abbruch: ');
    readln(eingabeY);
    eingabeY := upcase(eingabeY);
    case eingabeY of
      'X':
        begin
          cancel := TRUE;
          readInput := TRUE;
        end;
      '1' .. '7':
        begin
          gueltig := TRUE;
          y := StrToInt(eingabeY);
        end
    else
      readInput := FALSE;
    end;
  end;

  if not cancel and gueltig and isValidCoord(x, y) then
  begin
    writeln('Bitte ein ''B'' eingeben, wenn dort eine Bombe markiert werden ' +
      'soll, leer lassen zum Aufdecken oder ''X'' für Abbruch: ');
    readln(eingabeZ);
    eingabeZ := upcase(eingabeZ);
    case eingabeZ of
      'B':
        begin
          if ((field[x, y] = Bombe) and visible[x, y]) then
          begin
            bomb := TRUE;
            if field[x, y] = Bombe then
              visible[x, y] := FALSE;
            cancel := TRUE;
            writeln('PENG!!!');
          end;

        end;

      'X':
        begin
          readInput := TRUE;
          cancel := TRUE;
        end
    else
      begin
        readInput := FALSE;
        visible[x, y] := TRUE;
      end;
    end;
    visible[x, y] := TRUE;
  end;
end;

// Prüft, ob das gesamte Spielfeld mit Ausnahme der Bomben aufgedeckt ist
// @param
// field - Spielfeld, in dem geprüft werden soll
// visible -Sichtbarkeit der Zellen
// @out
//
// @return
// true, wenn alle Zellen außer die Bomben aufgedeckt sind
function isFieldSolved(field: TFIELD; visible: TVISIBLE): Boolean;
var
  x, y: TSize;
  z, b: integer;
begin
  z := 0;
  b := 0;
  for x := 1 to FIELDSIZE do
    for y := 1 to FIELDSIZE do
    begin
      if visible[x, y] then
        inc(z);
      if field[x, y] = Bombe then
        inc(b);
    end;
  isFieldSolved := z = FIELDSIZE * FIELDSIZE - b;
end;
// -------------------------------------------------------------------------------

var
  field: TFIELD;
  visible: TVISIBLE;
  cancel, bomb, gueltig: Boolean;
  x, y: TSize;
  dir: TDIR;
  xNachbar, yNachbar: integer;

begin
  gueltig := FALSE;
  initField(field, visible);
  repeat
    while not cancel do
    begin
      repeat
        printField(field, visible);
        setTextColor(15);
        cancel := readInput(x, y, cancel, bomb);
        visible[x, y] := TRUE;
        // weitere Zellen werden aufgedeckt, wenn in keiner Nachbarzelle eine Bombe ist
        if countBombs(field, x, y) = 0 then
          for dir := low(TDIR) to high(TDIR) do
          begin
            xNachbar := x + OFFSET_X[dir];
            yNachbar := y + OFFSET_Y[dir];
            if ((xNachbar > 0) and (xNachbar < 8) and (yNachbar > 0) and
              (yNachbar < 8)) then
              visible[xNachbar, yNachbar] := TRUE;
          end;
      until gueltig or cancel;

      if cancel then
      begin
        writeln('Ende');
        readln;
      end



      // wenn eine Bombe aufgedeckt wird

    end;
  until isFieldSolved(field, visible) or cancel;
  writeln('Tschüss! Bis zum nächsten Mal');
  readln;

end.

hoika 14. Jun 2021 05:01

AW: zweidimensionale Arrays
 
Hallo,
was sagt denn der Debugger?

Michael II 14. Jun 2021 09:01

AW: zweidimensionale Arrays
 
- Wähle das Menu Ansicht > Debug Fenster > Lokale Variablen.
Setze im Code an sinnvollen Stellen Breakpoints. Starte dein Programm. Dann siehst du sicher sofort, wo's noch nicht klappt.

- Wenn du in Funktionen auf den Rückgabewert Result zugreifen willst (beim Debuggen oft notwendig), musst du {$R+,Q+,X-} ersetzen durch {$R+,Q+}.

- In der Aufgabe steht: "FIELDSIZE für die Spielfeldbreite bzw. -höhe (das Feld ist also immer quadratisch). Initial könnt Ihr diese auf 7 setzen, andere Größen sollen aber auch möglich sein."
Fragen:
> Ist bei deinem Programm FELDSIZE=10 möglich?
> Wenn von dir jemand FELDSIZE=9 fordert, solltest du dein Programm innert fünf Sekunden anpassen können. Klappt dies?

- printFeld. Du lässt die Bomben zählen, aber der Fall n=8 wird von dir nicht bearbeitet.

Du schreibst:

Delphi-Quellcode:
      if not visible[x, y] then
      begin
        setTextColor(7);
        s := '&#9619;'
      end
      else if not visible[x, y] and (field[x, y] = Bombe) then
        s := 'ð'
      else
s := 'ð' wird nie ausgeführt, da du den Fall visible[x, y]=FALSE bereits vorher abgearbeitet hast. (Eventuell wäre eh if visible[x, y] and (field[x, y] = Bombe) näher an dem, was du willst? Und nach dem Abschnitt if not visible[x,y] then begin ...end weisst du dass visible[x,y] TRUE ist, also musst du darauf nicht testen. )


In printField schreibst du einen case Roman fürs Abarbeiten der Fälle n=0 bis 7.

Viel kürzer wäre dies: Definiere einen array farbe für die neun Fälle 0 bis 8:

Delphi-Quellcode:
procedure printField(field: TFIELD; visible: TVISIBLE);

const
  farbe : array[0..8] of word = ( 0,9,2,12,1,4,3,15,15 );

begin
...
      begin
        n := countBombs(field, x, y);
        setTextColor(farbe[n]);
        if n>0 then s:= inttostr(n) else s:= ' ';
      end;
...
end;

In der Funktion
function readInput(var x, y: TSize; var cancel, bomb: Boolean): Boolean;
schreibst du ziemlich am Anfang
readInput := gueltig or cancel or bomb;
Wozu diese Zeile gut sein soll...?

In der gleichen Funktion (readInput):
- Du initialisierst bomb, x, y nicht.
- Du definierst eine Variable field: TFIELD; Diese Variable enthält nutzlose Daten. (Deine Variable field in readInput und die global definierte Variable field haben ausser den Namen nix gemeinsam. Beim Debuggen würde dir auffallen, dass deine lokale field mit "Müll" gefüllt ist.)

readInput solltest du überdenken: 1. Du hast dort wo readInput steht (gemäss Aufgabe auch stehen muss), keinen Zugriff auf die globale Variable field. 2. Gemäss Aufgabenstellung muss readInput nur den User-Input prüfen.

Im Hauptprogramm schreibst du cancel := readInput...
readInput sollte aber gemäss Aufgabe TRUE zurückgeben, wenn der User sinnvolle Daten eingibt, sonst FALSE. So wie du's momentan schreibst wird cancel=TRUE, wenn der User sinnvolle Daten eingegeben hat.

Blup 14. Jun 2021 18:29

AW: zweidimensionale Arrays
 
Zitat:

Zitat von Mo53 (Beitrag 1491059)
Mein Programm steht jetzt mittlerweile fast, jedoch habe ich zwei Probleme an denen ich fast verzweifel, zum einen sieht das Programm nach der zweiten Abfrage jedes Zeichen als gültig und deckt sofort auf ohne überhaupt die dritte Abfrage auszuführen, zum anderen markiert das Programm das ausgewählte Feld nicht mit einer Bombe sobald ich auf b drücke, sondern deckt sofort auf. Ich wäre echt über jede Hilfe dankbar.

Lies dir deine Aufgabenstellung noch einmal genau durch:

Das Spiel endet, wenn alle Zellen außer denen mit Bomben aufgedeckt wurden, wenn der Spieler eine Bombe "aufgedeckt" hat, wenn er eine Zelle fälschlicherweise als Bombe markiert hat oder wenn bei einer der Eingaben mit x/X das Spiel abgebrochen wurde.

Einen dritten Zustand "nicht aufgedeckt aber mit Bombe markiert" kannst du in deiner Datenstruktur nicht abbilden.
Dafür müsste z.B. visible[x, y] drei Zustände annehmen können (also nicht nur True oder False).
Diese Regel in der Aufgabenstellung vereinfacht ein wenig das Programm.

Deshalb auch bei der Ausgabe:
Delphi-Quellcode:
   if not visible[x, y] then
   begin
     {...zugedeckte Felder darstellen...}
   end
   else
   begin
     {...aufgedeckte Felder darstellen...}
     if field[x, y] = Bombe then
     begin
       s := 'ð';
       SetColor({...});
     end    
     else {...usw...}


Alle Zeitangaben in WEZ +1. Es ist jetzt 08:14 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz