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('▓');
inc(x);
until x = FIELDSIZE + 1 ;
writeln;
if visible[x,y] = FALSE then
write('▓');
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