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:
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:
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