Aufgabe 4: Wetter in Quadratien

 

Vorüberlegungen

 

Als erstes ist zu überlegen, welche Wolken überhaupt über Quadratien hinwegziehen werden. Es ist zu erkennen, daß nur solche Wolken in Betracht kommen, die sich im Vorzeichen unterscheiden, z.B. (4/-1), oder bei denen bei einer Spalten-/Zeilennummer von Null die andere Nummer negativ ist, z.B. (0/-2). Zusätzlich dürfen die Grenzen von Quadratien nicht überschritten werden.

 

Mathematisch läßt sich dieser Zusammenhang wie folgt formulieren:

  1. Zeile * Spalte < 0
  2. Zeile * Spalte = 0 und Zeile + Spalte < 0
  3. Zeile <= Zeilemax und Spalte <= Spaltemax

Im Programm invertierte ich diese Bedingung, um alle falschen Eingaben abzufangen. Dann ergibt sich ein Gesamtterm, der bei Bewahrheitung zum Abbruch führt:

(((Spalte * Zeile >= 0) und (Spalte + Zeile >= 0)) oder (Spalte > Spaltemax) oder (Zeile > Zeilemax))

 

Um eine vollständige Wettervorhersage zu ermitteln, muß die Routine soviel Durchläufe, d.h. Takte, berechnen, bis keine Wolke mehr existiert. Eine Wolke verschwindet genau dann, wenn sie Quadratien verläßt oder abregnet.

 

Bei der Eingabe einer Wolke wird gleich ihre Bewegungsrichtung bestimmt. Die notwendigen Bedingungen formulieren sich wie folgt:

  1. West-Ost: wenn Spalte < Zeile
  2. Nord-Süd: wenn Spalte > Zeile

 

Sehr wichtig ist die Reihenfolge beim Verrücken der Wolken. In West-Ost-Richtung fange ich im Osten an und arbeite mich nach Westen durch. In Nord-Süd-Richtung beginne ich dementsprechend im Süden.

 

Da auch bestimmt werden soll, wo und wie oft es in Quadratien regnet, zählt für jedes Feld eine extra-Variable von 0 beginnend mit und wird bei jedem Regen um 1 erhöht.

 

Bedienung

 

Alle Koordinateneingaben erfolgen über die beiden Eingabefelder oben links. Drückt man Enter im Zeilenfeld, springt der Cursor automatisch ins Spaltenfeld weiter. Wenn man dort jedoch Enter drückt, wird an der entsprechenden Koordinate eine Wolke hinzugefügt. Bei ungültigen Zahlenwerten oder gar Buchstaben erfolgt eine Fehlermeldung.

 

Zu Beginn wird die Größe Quadratiens eingegeben. Dies erfolgt genauso wie die Eingabe einer Wolke. Allerdings sind die Zahlen um 1 größer als die Randkoordinate zu wählen, da die Zählung bei 0 statt 1 beginnt. Bei der Beispielaufgabe muß also 12/12 eingegeben werden, da sich dieses Quadratien von 0/0 bis 11/11 erstreckt und somit 12 Zeilen und 12 Spalten umfaßt. Der oberste Button ist noch mit Größe setzen beschriftet (siehe Bild 1).

 

Danach schaltet dieser Button auf Hinzu um und die neue Größe Quadratiens wird oben rechts angezeigt. Jede neue Wolke wird in der Listbox eingetragen. Außerdem wird Vorhersage nach der ersten eingegebene Wolke erst aktiviert, da vorher noch keine Wettervorhersage sinnvoll ist. In Bild 2 wurde dies für die Beispielaufgabe getan.

 

Wenn man fertig ist, genügt ein Mausklick auf Vorhersage, um eine Wetterprognose zu erhalten. Diese umfaßt neben der Gesamtregenhäufigkeit auch eine Aufschlüsselung in die einzelnen Regengebiete. Alle Felder, die trocken bleiben, werden nicht genannt. Für die Beispielaufgabe ergibt sich als Bildschirmausgabe Bild 3. Danach wird die Liste der Wolken gelöscht. Man kann jetzt eine neue Wetterlage eingeben, allerdings nur mit der gleichen Größe Quadratiens.

 

Bei der Eingabe werden mehrfache Nennungen nicht abgefangen. Aus Speicherplatzgründen habe ich mich auf ein Quadratien mit maximal 30 Spalten und 30 Zeilen beschränkt, d.h. von (0/0) bis (29/29). Die Wolken dürfen sich ebenfalls höchstens 30 Einheiten von der Grenze entfernt befinden. Allgemein gesagt darf der Betrag der Spalte bzw. Zeile 30 nicht übersteigen.

 

Programmdokumentation

 

Die Wetterlage wird in einem zweidimensionalem Feld nQuadratien gespeichert. Jedes Feldelement enthält je eine boolesche Variable bWestOst und bNordSued für eine mögliche West-Ost-Wolke und eine Nord-Süd-Wolke, sowie eine ordinale Variable nNiederschlag, die die Häufigkeit des Niederschlages mitzählt.

 

Allerdings muß ich noch auf einige Besonderheiten der Variablen hinweisen. In C++ werden Felder steht mit Startindex 0 erzeugt. In Pascal könnte ich die Grenzen Quadratien direkt als Index verwenden, in C++ hingegen muß ich umrechnen. Da dies häufig notwendig ist, habe ich die Makros GetX und GetY geschrieben. Ihre Kombination in der Anwendung auf nQuadratien wird durch das Makro GetQuadratien vereinfacht.

 

Die Bewegungsgeschwindigkeiten der Wolken wird durch die Makros TaktWestOst und TaktNordSued repräsentiert.

 

Wie gewohnt, beschränke ich mich wieder auf die Kernmethoden. Das sind diesmal die folgenden drei:

void OnAdd();

void OnForecast();

void UpdateSize();

 

OnAdd

 

Diese Funktion wird bei jeder vollständigen Eingabe von Zeile und Spalte aufgerufen. Da dies auch bei Programmbeginn geschieht, wird dann zur Größenfestlegung Quadratiens mittels UpdateSize weitergeleitet, wenn noch keine Grenzen festgelegt wurden, d.h. Zeilemax und Spaltemax Null sind. In diesem Fall terminiert auch OnAdd.

 

Nachdem die Zeile und die Spalte aus dem Dialog abgefragt wurden, wird ihre Gültigkeit anhand der Formel überprüft, welche unter Vorüberlegungen entwickelt wurde. Gegebenenfalls erfolgt eine Fehlermeldung und eine sofortige Beendigung der Methode.

 

Die Wolke wird nun in den Bildschirmdialog eingetragen. Als nächstes muß die Bewegungsrichtung ermittelt werden. Hier ziehe ich wiederum die oben benannten Bedingungen heran, die ihr Resultat gleich in das Feld nQuadratien mit den entsprechenden Koordinaten schreiben.

 

OnForecast

 

Der Button Vorhersage des Dialoges ist direkt mit dieser Funktion verknüpft. Hier wird die eigentliche Wettervorhersage erstellt.

 

Um irgendwelche Konflikte mit vorherigen Aufrufen zu unterbinden, erfolgt zuerst eine Initialisierung der Regenhäufigkeit mit 0. Dies bezieht sich sowohl auf jedes kleine Quadrat dieses Landes als auch auf die Gesamtsumme nRegen.

 

Danach beginnt eine längere Solange-Schleife. Die Bedingung bFertig wird zu Beginn auf falsch gesetzt, da ihre Negierung min. 1 Schleifendurchlauf sichern muß.

 

Jetzt folgen zwei verschachtelte Zählschleifen, die Quadratien von Osten nach Westen nach West-Ost-Wolken durchsuchen. Sie werden dann verrückt, allerdings nur wenn sie die Grenzen nicht überschreiten. Vorher wird der alte Platz als wolkenfrei markiert.

 

Danach wird genauso mit den Nord-Süd-Wolken verfahren, es wird von Süden nach Norden durchsucht. Auch werden Grenzüberschreitungen beachtet.

 

Nachdem die Wolkenbewegung für diesen Takt vollendet wurde, wird nach eventuell entstehenden Regen geforscht. Sind über einem Feld Quadratiens zwei Wolken, so regnen diese ab. Dies bedeutet, das die Regenwolken aus nQuadratien entfernt werden, die Regenhäufigkeit dort um 1 erhöht wird und auch die Gesamtregenhäufigkeit nRegen um 1 steigt.

 

Als letzter Teil der Schleife wird die Durchlaufbedingung nFertig überprüft. Es wird dazu davon ausgegangen, daß keine Wolke existiert und nFertig auf wahr gesetzt. Erkennt die komplette Durchsuchung von nQuadratien dennoch eine Wolke, ist nFertig falsch.

 

Somit wird die Solange-Schleife sooft durchlaufen, bis keine Wolke mehr vorhanden ist.

 

Abschließend wird eine Auswertung erstellt. Fällt überhaupt kein Regen (nRegen=0), erfolgt eine entsprechende Mitteilung. Ansonsten werden detailliert alle Regengebiete ausgegeben. Dazu wird nQuadratien wieder komplett durchlaufen und jedes befeuchtete Feld mit Niederschlagshäufigkeit erwähnt.

UpdateSize

 

Diese Funktion dient nur der Grenzfestlegung. Quadratien muß mindestens 1 Feld umfassen. Dazu wird die Eingabe von (0/0) abgefangen. Falls die Maximalgrenzen überschritten werden, erfolgt ebenfalls eine Fehlermeldung mit nachfolgendem Abbruch.

 

Nun werden in m_MaxX und m_MaxY die neuen Grenzen festgehalten. Sie sind um 1 niedriger als die Benutzereingaben. Abschließend erfolgt nur noch eine Anpassung des Bildschirms.

 

Beispiele

 

Anhand der oben eingebauten Bildschirmfotos ist ersichtlich, daß die gegebene und beispielhaft verwendete Aufgabe korrekt gelöst wird.

 

Das geforderte Beispiel ergibt: Gibt man keinerlei Wolken ein, erhält man:

 

 

 

 

 

 

 

 

 

 

 

 

 

Quelltext

 

VIER.H

 

#include "resource.h" // ID-Kennungen laden

 

 

class CVierApp : public CWinApp // Basisklasse

{

public:

virtual BOOL InitInstance(); // Start überladen

};

 

 

#define TaktWestOst 3 // Vorrücken in West-Ost-Richtung

#define TaktNordSued 2 // Vorrücken in Nord-Sued-Richtung

 

#define MaxX 30 // Maximale Spaltenanzahl

#define MaxY 30 // Maximale Zeilenanzahl

 

#define GetX(x) (x+MaxX) // Spalte umrechnen

#define GetY(y) (y+MaxY) // Zeile umrechnen

#define GetQuadratien(x,y) (nQuadratien[GetX(x)][GetY(y)])

// Koordinaten für Quadratien

// umrechnen

 

class CVierDlg : public CDialog // Dialogklasse

{

public:

CVierDlg(CWnd* pParent = NULL); // Konstruktor überladen

enum { IDD = ID_DIALOG }; // ID sichern

 

protected:

virtual BOOL OnInitDialog(); // Start verändern

virtual void OnOK(); // bei Enter

 

afx_msg void OnAdd(); // Wolke hinzufügen

afx_msg void OnForecast(); // Wettervorhersage

afx_msg void OnAbout(); // Programminfo

afx_msg void OnClose(); // Programmende

 

private:

void UpdateSize(); // Größe von Quadratien setzen

int m_MaxX; // Größe Quadratiens

int m_MaxY;

 

typedef struct // Typdefinition einer Wolke

{

int nNiederschlag; // Anzahl Niederschlag

BOOL bWestOst,bNordSued; // ist Wolke dort vorhanden ?

} tQuadrat;

tQuadrat nQuadratien[GetX(MaxX)+1][GetY(MaxY)+1];

// Quadratien deklarieren

DECLARE_MESSAGE_MAP() // Message-Handler einbinden

};

 

VIER.CPP

 

#include <afxwin.h> // MFC einbinden

#include "vier.h" // Header laden

 

 

CVierApp VierApp; // Programm statisch erzeugen

 

CVierApp::InitInstance() // Start überladen

{

Enable3dControls(); // 3D-Look

 

CVierDlg dlg; // Dialog statisch erzeugen

m_pMainWnd = &dlg; // als Hauptfenster setzen

dlg.DoModal(); // modal ausführen

 

return FALSE; // alles ok

}

 

 

BEGIN_MESSAGE_MAP(CVierDlg, CDialog) // Messages weiterleiten

ON_BN_CLICKED(ID_BUTTON_HINZU, OnAdd)

ON_BN_CLICKED(ID_BUTTON_VORHERSAGE, OnForecast)

ON_BN_CLICKED(ID_BUTTON_ABOUT, OnAbout)

ON_WM_CLOSE()

END_MESSAGE_MAP()

 

 

CVierDlg::CVierDlg(CWnd* pParent): CDialog(IDD, pParent)

{ // Konstruktor überladen

m_MaxX = m_MaxY = 0; // noch keine Grenzen gesetzt

 

memset(nQuadratien, 0, sizeof(nQuadratien));

// Quadratien als leer markieren

}

 

 

CVierDlg::OnInitDialog() // Start verändern

{

CDialog::OnInitDialog(); // Grundklasse aufrufen

 

SetDlgItemText(ID_BUTTON_HINZU, "&Größe setzen");

SetDlgItemText(ID_TEXT_SIZE, "Größe von Quadratien noch nicht festgesetzt.");

// Anwender an noch unbekannte

// Größe Quadratiens erinnern

((CButton*)GetDlgItem(ID_BUTTON_VORHERSAGE))->EnableWindow(FALSE);

return TRUE; // Focus kann Win95 setzen

}

 

 

void CVierDlg::OnOK() // bei Enter

{

if (GetFocus()==(CButton*)GetDlgItem(ID_EDIT_Y))

// falls Zeileneingabe

(CButton*)GetDlgItem(ID_EDIT_X)->SetFocus();

// ja, zur Spalteneingabe springen

else

OnAdd(); // sonst Wolke hinzufügen

}

 

 

void CVierDlg::OnAdd() // Wolke hinzufügen

{

if ((m_MaxX==0)&&(m_MaxY==0)) // noch keine Größe gesetzt ?

{

UpdateSize(); // Größe einlesen

return; // abbrechen

}

 

int nX = (int)GetDlgItemInt(ID_EDIT_X); // Spalte auslesen

int nY = (int)GetDlgItemInt(ID_EDIT_Y); // Zeile auslesen

 

if (((nX*nY>=0)&&(nX+nY>=0))||(nX>m_MaxX)||(nY>m_MaxY))

{ // ungültige Position ?

MessageBox("Wolke liegt bereits in Quadratien\noder wird Quadratien nie erreichen.",

"Problem",MB_OK|MB_ICONSTOP);// Fehlermeldung ausgeben

return; // abbrechen

}

 

char cWolke[15] = ""; // temporär

sprintf(cWolke, "%i/%i", nY, nX); // Koordinaten als Zeichenkette

((CListBox*)GetDlgItem(ID_LISTBOX))->InsertString(-1, cWolke);

// an die Listbox anhängen

GetQuadratien(nX,nY).bWestOst = BOOL(nX<nY);

GetQuadratien(nX,nY).bNordSued = BOOL(nX>nY);

// in Quadratien eintragen, dabei

// Richtung bestimmen

SetDlgItemText(ID_EDIT_X, ""); // Eingabefelder löschen

SetDlgItemText(ID_EDIT_Y, "");

 

((CButton*)GetDlgItem(ID_BUTTON_VORHERSAGE))->EnableWindow(TRUE);

 

(CButton*)GetDlgItem(ID_EDIT_Y)->SetFocus();// Focus auf Zeile setzen

}

 

 

void CVierDlg::OnForecast() // Wettervorhersage

{

int nX, nY;

int nRegen = 0;

 

for (nX=0; nX<m_MaxX; nX++) // überall Niederschlagsmenge auf

for (nY=0; nY<m_MaxY; nY++) // 0 setzen

GetQuadratien(nX,nY).nNiederschlag = 0;

 

BOOL bFertig = FALSE; // Schleifenbedingung

while (!bFertig)

{

for (nX=m_MaxX; nX>-MaxX; nX--)

for (nY=-MaxY; nY<m_MaxY; nY++)

if (GetQuadratien(nX,nY).bWestOst)

{ // ostwärts ziehende Wolke

GetQuadratien(nX,nY).bWestOst = FALSE;

// Wolke löschen

if (nX+TaktWestOst<=m_MaxX) // falls nächster Takt auch innerhalb

// Quadratiens

GetQuadratien(nX+TaktWestOst,nY).bWestOst = TRUE;

// Wolke an neuem Platz setzen

}

 

for (nY=m_MaxY; nY>-MaxY; nY--)

for (nX=-MaxX; nX<m_MaxX; nX++)

if (GetQuadratien(nX,nY).bNordSued)

{ // westwärts ziehende Wolke

GetQuadratien(nX,nY).bNordSued = FALSE;

// Wolke löschen

if (nY+TaktNordSued<=m_MaxY)// falls nächster Takt auch innerhalb

// Quadratiens

GetQuadratien(nX,nY+TaktNordSued).bNordSued = TRUE;

// Wolke an neuem Platz setzen

}

 

for (nX = 0; nX<m_MaxX; nX++)

for (nY = 0; nY<m_MaxY; nY++)

if (GetQuadratien(nX,nY).bNordSued&&

GetQuadratien(nX,nY).bWestOst)

// zwei Wolken über einem Feld

{

GetQuadratien(nX,nY).bNordSued = FALSE;

GetQuadratien(nX,nY).bWestOst = FALSE;

// beide Wolken löschen

GetQuadratien(nX,nY).nNiederschlag++;

// Niederschlag markieren

nRegen++; // Gesamtniederschlag erhöhen

}

bFertig = TRUE; // von leerem Quadratiens ausgehen

for (nX = -MaxX; nX<m_MaxX; nX++)

for (nY = -MaxY; nY<m_MaxY; nY++)

if (GetQuadratien(nX,nY).bNordSued||

GetQuadratien(nX,nY).bWestOst)

bFertig = FALSE; // falls eine Wolke gefunden,

// neuer Schleifendurchlauf nötig

}

 

if (nRegen==0) // falls überhaupt kein Regen

MessageBox("Es wird in Quadratien überhaupt nicht regnen.","Ergebnis",

MB_OK|MB_ICONINFORMATION); // Meldung ausgeben

else

{

char cHeadline[50] = ""; // Überschrift

sprintf(cHeadline, "Es wird in Quadratien %imal regnen:\n", nRegen);

char cAusgabe[1000] = ""; // genaue Vorhersage

for (nX = 0; nX<MaxX; nX++)

for (nY = 0; nY<MaxY; nY++)

if (GetQuadratien(nX,nY).nNiederschlag)

// falls Niederschlag

sprintf(cAusgabe, "%s%imal in Quadrat (%i,%i)\n", cAusgabe,

GetQuadratien(nX,nY).nNiederschlag, nY, nX);

// Koordinaten und Menge merken

MessageBox(cAusgabe, cHeadline, MB_OK|MB_ICONINFORMATION);

// anzeigen

}

 

((CListBox*)GetDlgItem(ID_LISTBOX))->ResetContent();

}

 

 

void CVierDlg::OnAbout() // Programminfo

{

MessageBox("geschrieben von Stephan Brumme\nin Watcom C++ 10.6 mit MFC 3.2",

"Über...",MB_OK|MB_ICONINFORMATION);

}

 

 

void CVierDlg::OnClose() // Programmende

{

if (MessageBox("Wirklich beenden ?","Ende",MB_YESNO|MB_ICONQUESTION)==IDYES)

CDialog::OnClose(); // Sicherheitsabfrage

}

 

 

void CVierDlg::UpdateSize() // Größe von Quadratien setzen

{

int nX = (int)GetDlgItemInt(ID_EDIT_X); // Spalte lesen

int nY = (int)GetDlgItemInt(ID_EDIT_Y); // Zeile lesen

 

if ((nX<1)||(nY<1)) // falls zu klein

{

MessageBox("Quadratien muß min. 1 Quadranten enthalten.\nAußerdem sind nur positive Quadrantenkoordinaten als Grenzen erlaubt.",

"Problem",MB_OK|MB_ICONSTOP);// Fehlermeldung

return; // Abbruch

}

 

if ((nX>=MaxX)||(nY>=MaxY)) // falls zu groß

{

MessageBox("Dieses Quadratien ist zu groß.","Problem",MB_OK|MB_ICONSTOP);

return; // Fehlermeldung, abbrechen

}

 

m_MaxX = nX-1; // Eckpunkt im Südosten bestimmen

m_MaxY = nY-1;

 

char cSize[50] = ""; // für Bildschirmausgabe

sprintf(cSize, "Größe von Quadratien:\n von 0/0 bis %i/%i", m_MaxY, m_MaxX);

SetDlgItemText(ID_TEXT_SIZE, cSize); // setzen

 

SetDlgItemText(ID_BUTTON_HINZU, "&Hinzu"); // Button ändern

 

SetDlgItemText(ID_EDIT_X, ""); // Eingabefelder löschen

SetDlgItemText(ID_EDIT_Y, "");

 

(CButton*)GetDlgItem(ID_EDIT_Y)->SetFocus();// Focus setzen

}

 

VIER.DLG

 

100 DIALOG FIXED IMPURE 20, 40, 160, 100

STYLE DS_MODALFRAME | DS_3DLOOK | DS_CENTER | DS_CENTERMOUSE | WS_OVERLAPPED | WS_CAPTION | WS_VISIBLE | WS_SYSMENU

CAPTION "Aufgabe 4"

FONT 8, "Helv"

BEGIN

CONTROL "", 101, "EDIT", ES_LEFT | ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 30, 5, 40, 12

CONTROL "", 102, "EDIT", ES_LEFT | ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 30, 20, 40, 12

CONTROL "&Größe setzen", 103, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 5, 40, 65, 14

CONTROL "&Vorhersage", 104, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 5, 60, 65, 14

CONTROL "", 106, "LISTBOX", LBS_STANDARD | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL | WS_TABSTOP, 80, 25, 75, 74

CONTROL "Zeile:", 107, "STATIC", SS_LEFT | WS_CHILD | WS_VISIBLE, 5, 5, 21, 8

CONTROL "Spalte:", 108, "STATIC", SS_LEFT | WS_CHILD | WS_VISIBLE, 5, 20, 23, 8

CONTROL "&Autor", 105, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 5, 80, 65, 14

CONTROL "", 109, "STATIC", SS_LEFT | WS_CHILD | WS_VISIBLE, 75, 6, 79, 17

END

 

RESOURCE.H

 

#define ID_DIALOG 100

#define ID_EDIT_Y 101

#define ID_EDIT_X 102

#define ID_BUTTON_HINZU 103

#define ID_BUTTON_VORHERSAGE 104

#define ID_BUTTON_ABOUT 105

#define ID_LISTBOX 106

#define ID_TEXT_SIZE 109