(************************************************************************************

:Program.       Mine

:Contents.      neue Implementation eines alten Computer-Spiels

:Remark.        benötigt KICK 2.00^


:Copyright.     "freely distributable copyrighted software"

:Author.        Thomas Ansorge

:Address.       Dinkelackerring 55, W-6730 Neustadt, Deutschland, Europa


:Language.      Modula-2

:Translator.    M2Amiga V4.096 (deutsch)


:Imports.       BeepSupport [Thomas Ansorge]
:Imports.       Images [Thomas Ansorge]
:Imports.       OptReqL [Thomas Ansorge]
:Imports.       OptReqToolsL [Thomas Ansorge]
:Imports.       ReqSupport [Thomas Ansorge]

:Support.       reqtools.library (Nico François) falls vorhanden, sonst
:Support.       req.library (Colin Fox, Bruce Dawson) falls vorhanden


:Version.       1.0 vom 08. August 1992

:History.       1.0 vom 08.08.1992: es läuft.


************************************************************************************)


(*$ DEFINE Deutsch := TRUE *)

MODULE Mine;

FROM Arts IMPORT Assert, kickVersion;

FROM BeepSupport IMPORT Beep, infoPri, Sound;

FROM Conversions IMPORT ValToStr;

FROM DiskFontL IMPORT OpenDiskFont;

FROM DosD IMPORT Date, DatePtr;

FROM DosL IMPORT DateStamp;

FROM ExecL IMPORT GetMsg, ReplyMsg, WaitPort;

FROM FileSystem IMPORT Close, File, Lookup, ReadByteBlock, Response, WriteByteBlock;

FROM GadToolsD IMPORT buttonIDCMP, buttonKind, GtTags, NewGadget, NewGadgetFlags,
   NewGadgetFlagSet, sliderIDCMP, sliderKind;

FROM GadToolsL IMPORT CreateContext, CreateGadgetA, DrawBevelBoxA, FreeGadgets,
   FreeVisualInfo, GetVisualInfoA;

FROM GraphicsD IMPORT FontFlagSet, FontStyleSet, jam2, TextAttr, TextFontPtr;

FROM GraphicsL IMPORT CloseFont;

IMPORT Images;

FROM InputEvent IMPORT Qualifiers, QualifierSet;

FROM IntuitionD IMPORT GadgetPtr, IDCMPFlags, IDCMPFlagSet, Image, IntuiMessage,
   IntuiMessagePtr, IntuiText, PropInfo, PropInfoPtr, Screen, ScreenPtr, WaTags,
   Window, WindowFlags, WindowFlagSet, WindowPtr;

FROM IntuitionL IMPORT AddGList, CloseWindow, DrawImage, LockPubScreen, ModifyIDCMP,
   OpenWindowTagList, PrintIText, RefreshGadgets, RemoveGList, UnlockPubScreen;

FROM OptReqL IMPORT reqBase;

FROM OptReqToolsL IMPORT reqtoolsBase;

IMPORT R;

FROM RandomNumber IMPORT PutSeed, RND;

FROM RequesterSupport IMPORT noError, ReqLib, TextRequest, StringRequest;

FROM String IMPORT Concat, ConcatChar, Copy;

FROM SYSTEM IMPORT ADDRESS, ADR, ASSEMBLE, CAST, TAG;

FROM UtilityD IMPORT tagEnd;

(* ------------------------------------------------------------------------------- *)

(* die Farbverteilung auf der Standard-Workbench *)

CONST grau    = 0;
      schwarz = 1;
      weiss   = 2;
      blau    = 3;

CONST mineStr = "Mine";

(* TAG-Listen *)

CONST maxTags = 9;

(* der Font *)

CONST fontHoehe = 8;
      fontName  = "topaz.font";

(*$ IF Deutsch *)

CONST info = "Infos und Hilfe";

      keinRuhm = "auf eine Mine getreten, nicht gut genug für die Ruhmeshalle ...\n" +
                 "\n" +
                 "                 Jetzt kommt ein Satz mit \"x\":\n" +
                 "\n" +
                 "                         Das war nix!";

      kick20 = "Mine benötigt KICK 2.00^!";

      openWindowError = "konnte Fenster nicht öffnen!";

      ruhmeshalleFensterName = "Mine Ruhmeshalle für n = ";

      ruhmeshalleGadgetName = "Ruhmeshalle ansehen";

      seitenlaenge = "Seitenlänge:";

      stringReqTitle = "Mine: Ihren Namen bitte:";

      weiter = "weiter";

      zeitS = "Zeit: ";

(*$ ELSE *)

CONST info = "infos and help";

      keinRuhm = "I'm sorry, but there was a mine...\n" +
                 "and you're not good enough for the highscore list, I regret.";

      kick20 = "Mine needs KICK 2.00^!";

      openWindowError = "could not open window!";

      ruhmeshalleFensterName = "Mine Highscores for n = ";

      ruhmeshalleGadgetName = "show Highscores";

      seitenlaenge = "field size :";

      stringReqTitle = "Mine: Please enter your name:";

      weiter = "continue";

      zeitS = "Time: ";

(*$ ENDIF *)

(* Das Spielfeld *)

CONST boxX         = 24;
      boxY         = 12;
      fahne        = -20; (* wird auf Wert addiert *)
      horoffset    = 1;
      mine         = -1;
      nummer       = -3;
      randBreite   = 2;

(* Das quadratische Spielfeld *)

TYPE Spielfeld = ARRAY [1..77], [1..77] OF SHORTINT;

TYPE Zeit = RECORD
               ticks    : [0..49];
               minuten  : LONGINT;
               sekunden : LONGINT;
               zeitStr  : ARRAY [0..11] OF CHAR;
               hilfeStr : ARRAY [0..2] OF CHAR;
               zeitIText: IntuiText;
            END (* RECORD Zeit *);

(* Die Ruhmeshalle *)

CONST ruhmeshalleName = "Mine-Highscores_";

TYPE StringRuhm = ARRAY [0..26] OF CHAR; (* der komplette String mit allem drin! *)

     RuhmeshalleEintrag = RECORD
                             anzM: LONGINT;
                             zeit: RECORD
                                      min,
                                      sec: LONGINT;
                                   END (* RECORD Zeit *);
                             name: StringRuhm;
                          END (* RECORD *);

     Ruhmeshalle = ARRAY [1..10] OF RuhmeshalleEintrag;

VAR anzFahnen       : LONGINT;
    anzMinen        : LONGINT;
    anzNummer       : LONGINT;
    bombeImage      : Image;
    clickedImage    : Image;
    clickmeImage    : Image;
    contextPtr      : GadgetPtr;
    date            : Date;
    eintrag         : RuhmeshalleEintrag;
    ende            : BOOLEAN;
    err             : BOOLEAN;
    explosionImage  : Image;
    fahneImage      : Image;
    flags           : IDCMPFlagSet;
    fontAttr        : TextAttr;
    fontPtr         : TextFontPtr;
    gadgetPtr       : GadgetPtr;
    gList           : GadgetPtr;
    i               : INTEGER;
    innerWidth      : LONGINT;
    innerHeight     : LONGINT;
    intuiText       : IntuiText;
    j               : INTEGER;
    mausX, mausY    : INTEGER;
    nachricht       : IntuiMessagePtr;
    newGadget       : NewGadget;
    pos             : LONGINT; (* Platz in der Ruhmeshalle *)
    propInfoPtr     : PropInfoPtr;
    qualifiers      : QualifierSet;
    reqLib          : ReqLib;
    ruhmeshalle     : Ruhmeshalle;
    ruhmeshalleEintr: RuhmeshalleEintrag;
    screenPtr       : ScreenPtr;
    spielfeld       : Spielfeld;
    spielfeldMax    : LONGINT;
    spielFeldMax    : LONGINT;
    string2         : ARRAY [0..2] OF CHAR;
    string30        : ARRAY [0..30] OF CHAR;
    string80        : ARRAY [0..80] OF CHAR;
    tagList         : ARRAY [1..2 * maxTags] OF LONGINT;
    visualInfo      : ADDRESS;
    windowPtr       : WindowPtr;
    windowPtr2      : WindowPtr;
    zeit            : Zeit;

(* ------------------------------------------------------------------------------- *)

PROCEDURE AnzMinen (spielfeldMax: LONGINT): LONGINT;

   BEGIN (* Funktion AnzMinen *)

   RETURN (spielfeldMax * spielfeldMax) DIV 6;
END AnzMinen (* Funktion *);

(* ------------------------------------------------------------------------------- *)

PROCEDURE Eor (z1{R.D0}, z2{R.D1}: LONGINT): LONGINT;

   BEGIN (* Funktion Eor *)

   ASSEMBLE (
      EOR.L D1, D0
   END);

   RETURN z1;
END Eor (* Funktion *);

(* ------------------------------------------------------------------------------- *)

PROCEDURE FuegeEintragEin (VAR ruhmeshalle: Ruhmeshalle;
                           eintrag        : RuhmeshalleEintrag;
                           pos            : LONGINT);

   VAR err  : BOOLEAN;
       i    : [1..10];
       hilfe: ARRAY [0..2] OF CHAR;
       str  : ARRAY [0..SIZE (eintrag.name)] OF CHAR;

   (* ---------------------------------------------------------------------------- *)

   BEGIN (* Prozedur FuegeEintragEin *)

   (* Platz schaffen *)

   FOR i := 9 TO pos BY -1 DO
      ruhmeshalle [i + 1].anzM := ruhmeshalle [i].anzM;
      ruhmeshalle [i + 1].zeit.min := ruhmeshalle [i].zeit.min;
      ruhmeshalle [i + 1].zeit.sec := ruhmeshalle [i].zeit.sec;
      Copy (ruhmeshalle [i + 1].name, ruhmeshalle [i].name);
   END (* FOR i *);

   (* eintragen *)

   ValToStr (eintrag.anzM, FALSE, str, 10, 3, " ", err);
   ConcatChar (str, " ");
   ValToStr (eintrag.zeit.min, FALSE, hilfe, 10, 2, "0", err);
   Concat (str, hilfe);
   ConcatChar (str, ":");
   ValToStr (eintrag.zeit.sec, FALSE, hilfe, 10, 2, "0", err);
   Concat (str, hilfe);
   ConcatChar (str, " ");
   Concat (str, eintrag.name);

   WITH ruhmeshalle [pos] DO
      anzM := eintrag.anzM;
      zeit.min := eintrag.zeit.min;
      zeit.sec := eintrag.zeit.sec;
      Copy (name, str);
   END (* WITH *);
END FuegeEintragEin (* Prozedur *);

(* ------------------------------------------------------------------------------- *)

PROCEDURE Info (windowPtr: WindowPtr);

   (* zeigt via Text-Requester Infos und Hilfen zum Spiel an *)

   (*$ IF Deutsch *)

   CONST quitGad = "Quit";

   CONST shareware = (* war mal Shareware, daher der Name *)
      "                          Mine © 1992 by\n" +
      "\n" +
      "     Thomas Ansorge, Dinkelackerring 55, W-6730 Neustadt/Weinstaße,\n" +
      "                        Deutschland, Europa\n" +
      "\n" +
      "\n" +
      "\"Mine\" besteht aus den Dateien Mine.deutsch, Mine.english, Mine.dok,\n" +
      "Mine.doc und Mine-Quelltexte.lha.\n" +
      "\n" +
      "Mine ist \"freely distributable copyrighted software\". Sie dürfen es nur\n" +
      "auf nicht-kommerzieller Basis und nur als Ganzes weiterverbreiten und\n" +
      "benutzen, aber alle anderen Rechte bleiben bei mir.\n" +
      "\n" +
      "Die (insgesamt 73) Dateien Mine-Highscores_?? sind von Mine verwaltete\n" +
      "Highscore-Listen, die zwar zu Mine gehören, aber beim Weiterkopieren\n" +
      "weggelassen werden dürfen. Sie sollten allerdings dabei sein.\n" +
      "\n" +
      "Mit Ausnahme der Highscore-Listen (dort ist es erwünscht) darf Mine nicht\n" +
      "verändert werden.\n" +
      "\n" +
      "Da ich für Mine kein Geld verlange, sehe ich auch nicht ein, weshalb ich\n" +
      "irgendeine Verantwortung für Schäden, die durch Mine direkt oder indirekt\n" +
      "verursacht werden, aufkommen soll. SIE BENUTZEN MINE AUF EIGENE GEFAHR!";

   CONST sharewareTitel = "Mine:";

   CONST spielregeln =
      "Das Spiel Mine funktioniert nach folgenden Regeln:\n" +
      "\n" +
      "Sie haben ein quadratisches Spielfeld mit n * n Feldern. Zufällig über\n" +
      "diese Felder verteilt liegen INTEGER (n * n / 6) Minen versteckt. Diese\n" +
      "gilt es möglichst schnell zu finden. Dazu können Sie die Felder mit der\n" +
      "linken Maustaste anklicken. Ist auf dem Feld eine Mine, explodiert sie\n" +
      "und das Spiel ist zu Ende. Ist keine Mine auf dem Feld, dann enthüllt es\n" +
      "die Anzahl an Minen, die diesem Feld direkt benachbart sind (waagrecht,\n" +
      "senkrecht und diagonal). Glauben Sie eine Mine lokalisiert zu haben, dann\n" +
      "können Sie das entsprechende Feld mit der rechten Maustaste markieren.\n" +
      "\n" +
      "Das Spiel ist zu Ende, wenn eine Mine explodiert oder wenn alle Felder\n" +
      "mit Minen markiert und alle anderen aufgedeckt sind.\n" +
      "\n" +
      "Am Ende entscheiden die Anzahl an markierten Minen und die benötigte\n" +
      "Zeit, ob Sie in die Ruhmeshalle kommen oder nicht.";

   CONST spielregelnGad = "Spielregeln";

   CONST spielregelnTitel = "Mine: Die Spielregeln";

   (*$ ELSE *)

   CONST quitGad = "Quit";

   CONST shareware =
      "                          Mine © 1992 by\n" +
      "\n" +
      "     Thomas Ansorge, Dinkelackerring 55, W-6730 Neustadt/Weinstraße,\n" +
      "                          Germany, Europe\n" +
      "\n" +
      "\n" +
      "Mine is freely distributable copyrighted software.  You have permission to\n" +
      "distribute Mine to anyone you want on a non-commercial base and as long as\n" +
      "Mine is complete.  All other rights remain at the author alone.\n" +
      "\n" +
      "You are not allowed to change any part of Mine except for the highscore\n" +
      "lists (where you are supposed to...).\n" +
      "\n" +
      "I do not ask for money if you like Mine and can not be made liable for any\n" +
      "damage Mine does to anyone or anything directly or indirectly.  YOU USE\n" +
      "MINE ON YOUR OWN RISK!";

   CONST sharewareTitel = "Mine:";

   CONST spielregeln =
      "To play mine, you have to follow these simple rules:\n" +
      "\n" +
      "Your field is a n * n square with INTEGER (n * n / 6) hidden mines and\n" +
      "all you have to do is to mark them with a nice flag so that nobody\n" +
      "stumbles over them. The problem: As I said, they are hidden, so you\n" +
      "don't see them. But there is help available: If you click with your\n" +
      "left mouse button on a field that does not contain a mine, it will tell\n" +
      "you the number of mines hidden in its direct neighbourhood. If you hit\n" +
      "a mine with the left mouse button, the game is over. With your right\n" +
      "mouse button you can mark any field with a flag or collect a flag from\n" +
      "a field. You have as many flags as there are mines.\n" +
      "\n" +
      "The game is over if you hit a mine with your left mouse button or if\n" +
      "there is no field without either a number or a flag in it left. Then\n" +
      "the program has a look at the number of mines you marked with a flag\n" +
      "and the time it took and then perhaps you enter the highscorelist.";

   CONST spielregelnGad = "how to play Mine";

   CONST spielregelnTitel = "Mine: The rules of the game:";

   (*$ ENDIF *)

   CONST sharewareGads = spielregelnGad + "|" + quitGad;

   VAR flags : IDCMPFlagSet;
       nummer: LONGINT;

   (* ---------------------------------------------------------------------------- *)

   BEGIN (* Prozedur Info *)

   flags := IDCMPFlagSet {};

   REPEAT
      nummer := TextRequest (shareware, NIL, sharewareGads, sharewareTitel, reqLib,
         flags, windowPtr);

      CASE nummer OF
         |1: nummer := TextRequest (spielregeln, NIL, quitGad, spielregelnTitel,
                reqLib, flags, windowPtr);

             nummer := 1; (* nicht 0 *)

         |0: (* QUIT: nichts *);
      END (* CASE nummer *);
   UNTIL nummer = 0;
END Info (* Prozedur *);

(* ------------------------------------------------------------------------------- *)

PROCEDURE InitImages;

   (* initialisiert die Images *)

   (* alles ist global *)

   BEGIN (* Prozedur InitImages *)

   WITH bombeImage DO
      leftEdge :=0;
      topEdge :=0;
      width := boxX;
      height := boxY;
      depth := 2;
      imageData := ADR (Images.BombePlane);
      planePick := 3;
      planeOnOff := 0;
      nextImage := NIL;
   END (* WITH bombeImage *);

   WITH clickedImage DO
      leftEdge :=0;
      topEdge :=0;
      width := boxX;
      height := boxY;
      depth := 2;
      imageData := ADR (Images.ClickedPlane);
      planePick := 3;
      planeOnOff := 0;
      nextImage := NIL;
   END (* WITH clickedImage *);

   WITH clickmeImage DO
      leftEdge :=0;
      topEdge :=0;
      width := boxX;
      height := boxY;
      depth := 2;
      imageData := ADR (Images.ClickMePlane);
      planePick := 3;
      planeOnOff := 0;
      nextImage := NIL;
   END (* WITH clickmeImage *);

   WITH explosionImage DO
      leftEdge :=0;
      topEdge :=0;
      width := boxX;
      height := boxY;
      depth := 2;
      imageData := ADR (Images.ExplosionPlane);
      planePick := 3;
      planeOnOff := 0;
      nextImage := NIL;
   END (* WITH explosionImage *);

   WITH fahneImage DO
      leftEdge :=0;
      topEdge :=0;
      width := boxX;
      height := boxY;
      depth := 2;
      imageData := ADR (Images.FahnePlane);
      planePick := 3;
      planeOnOff := 0;
      nextImage := NIL;
   END (* WITH fahneImage *);
END InitImages (* Prozedur *);

(* ------------------------------------------------------------------------------- *)

PROCEDURE LadeRuhmeshalle (VAR ruhmeshalle: Ruhmeshalle;
                           ruhmeshalleName: ARRAY OF CHAR);

   (* lädt bzw initialisiert mit 0 die Variable ruhmeshalle *)

   VAR file: File;
       i   : [1..10];

   (* ---------------------------------------------------------------------------- *)

   BEGIN (* Prozedur LadeRuhmeshalle *)

   Lookup (file, ruhmeshalleName, SIZE (ruhmeshalle), FALSE);

   IF file.res = done THEN
      ReadByteBlock (file, ruhmeshalle);
      Close (file);

   ELSE (* IF file.res *);
      FOR i := 1 TO 10 DO
         WITH ruhmeshalle [i] DO
            anzM := 0;

            WITH zeit DO
               min := 99;
               sec := 59;
            END (* WITH zeit *);

            name := "";
         END (* WITH ruhmeshalle [i] *);
      END (* FOR i *);
   END (* IF file.res *);
END LadeRuhmeshalle (* Prozedur *);

(* ------------------------------------------------------------------------------- *)

PROCEDURE MaxAnzahlFelder (screenPtr: ScreenPtr): LONGINT;

   (* stellt fest, wie groß das auf dem Screen screenPtr gezeichnete Feld maximal  *)
   (* sein kann, damit das ganze Fenster draufpaßt                                 *)

   VAR horiz, vert: LONGINT;

   (* ---------------------------------------------------------------------------- *)

   BEGIN (* Funktion MaxAnzahlFelder *)

   horiz := (screenPtr^.width - 2 * 3 - 2 * 4) DIV (boxX + 1);
   vert := (screenPtr^.height - 3 * randBreite - 8 -
      LONGINT (screenPtr^.font^.ySize + CARDINAL (screenPtr^.wBorTop) + 1)) DIV boxY;

   IF horiz <= vert THEN
      RETURN horiz;

   ELSE
      RETURN vert;
   END (* IF horiz *);
END MaxAnzahlFelder (* Funktion *);

(* ------------------------------------------------------------------------------- *)

PROCEDURE Pos (ruhmeshalle: Ruhmeshalle;
               eintrag    : RuhmeshalleEintrag (* ohne name *)
              ): LONGINT;

   (* stellt den Platz des neuen eintrags in der Ruhmeshalle fest *)

   VAR pos: LONGINT;

   (* ---------------------------------------------------------------------------- *)

   BEGIN (* Prozedur Pos *)

   pos := 1;

   WHILE (eintrag.anzM < ruhmeshalle [pos].anzM) AND (pos < 10) DO
      INC (pos);
   END (* WHILE *);

   IF (pos = 10) AND (eintrag.anzM < ruhmeshalle [pos].anzM) THEN
      RETURN 11;
   END (* IF (pos *);

   IF eintrag.anzM > ruhmeshalle [pos].anzM THEN
      RETURN pos;
   END (* IF eintrag.anzM *);

   WHILE (eintrag.zeit.min > ruhmeshalle [pos].zeit.min) AND (pos < 10) AND
      (eintrag.anzM = ruhmeshalle [pos].anzM) DO
      INC (pos);
   END (* WHILE *);

   IF (pos = 10) AND (eintrag.zeit.min > ruhmeshalle [pos].zeit.min) THEN
      RETURN 11;
   END (* IF (pos *);

   IF eintrag.zeit.min < ruhmeshalle [pos].zeit.min THEN
      RETURN pos;
   END (* IF eintrag.zeit.min *);

   WHILE (eintrag.zeit.sec >= ruhmeshalle [pos].zeit.sec) AND (pos < 10) AND
      (eintrag.anzM = ruhmeshalle [pos].anzM) AND
      (eintrag.zeit.min = ruhmeshalle [pos].zeit.min) DO
      INC (pos);
   END (* WHILE *);

   IF (pos = 10) AND (eintrag.zeit.sec >= ruhmeshalle [pos].zeit.sec) THEN
      RETURN 11;
   END (* IF (pos *);

   RETURN pos;
END Pos (* Funktion *);

(* ------------------------------------------------------------------------------- *)

PROCEDURE SaveRuhmeshalle (ruhmeshalle    : Ruhmeshalle;
                           ruhmeshalleName: ARRAY OF CHAR);

   (* speichert die Ruhmeshalle *)

   VAR file: File;

   (* ---------------------------------------------------------------------------- *)

   BEGIN (* Prozedur SaveRuhmeshalle *)

   Lookup (file, ruhmeshalleName, SIZE (ruhmeshalle), TRUE);

   WriteByteBlock (file, ruhmeshalle);

   Close (file);
END SaveRuhmeshalle (* Prozedur *);

(* ------------------------------------------------------------------------------- *)

PROCEDURE VerteileMinen (VAR spielfeld: Spielfeld;
                         spielfeldMax : LONGINT;
                         anzMinen     : LONGINT);

   (* initialisiert das Spielfeld *)

   VAR i, j, k: LONGINT;

   (* ---------------------------------------------------------------------------- *)

   BEGIN (* Prozedur VerteileMinen *)

   (* Feld löschen <=> mit 0 füllen *)

   FOR i := 1 TO spielfeldMax DO
      FOR j := 1 TO spielfeldMax DO
         spielfeld [i, j] := 0;
      END (* FOR j *);
   END (* FOR i *);

   (* Minen nach Zufallsprinzip verteilen und Felder drumherum aktualisieren *)

   k := 0;

   REPEAT
      i := RND (spielfeldMax) + 1;
      j := RND (spielfeldMax) + 1;

      IF spielfeld [i, j] # mine THEN
         (* eine neue Mine *)
         spielfeld [i, j] := mine;
         INC (k);

         IF i # 1 THEN
            IF j # 1 THEN
               IF spielfeld [i - 1, j - 1] # mine THEN
                  INC (spielfeld [i - 1, j - 1]); (* links oben *)
               END;
            END;

            IF spielfeld [i - 1, j] # mine THEN
               INC (spielfeld [i - 1, j]); (* oben *)
            END;

            IF j # spielfeldMax THEN
               IF spielfeld [i - 1, j + 1] # mine THEN
                  INC (spielfeld [i - 1, j + 1]); (* rechts oben *)
               END;
            END;
         END (* IF i # 1 *);

         IF j # 1 THEN
            IF spielfeld [i, j - 1] # mine THEN
               INC (spielfeld [i, j - 1]); (* links *)
            END;
         END;

         IF j # spielfeldMax THEN
            IF spielfeld [i, j + 1] # mine THEN
               INC (spielfeld [i, j + 1]); (* rechts *)
            END;
         END;

         IF i # spielfeldMax THEN
            IF j # 1 THEN
               IF spielfeld [i + 1, j - 1] # mine THEN
                  INC (spielfeld [i + 1, j - 1]); (* links unten *)
               END;
            END;

            IF spielfeld [i + 1, j] # mine THEN
               INC (spielfeld [i + 1, j]); (* unten *)
            END;

            IF j # spielfeldMax THEN
               IF spielfeld [i + 1, j + 1] # mine THEN
                  INC (spielfeld [i + 1, j + 1]); (* rechts unten *)
               END;
            END;
         END (* IF i # spielfeldMax *);
      END (* IF spielfeld [i, j] # mine *);
   UNTIL k = anzMinen;
END VerteileMinen (* Prozedur *);

(* ------------------------------------------------------------------------------- *)

PROCEDURE Ziffer (ziffer: SHORTINT): CHAR;

   (* Anzahl der Minen um ein Feld *)

   BEGIN (* Funktion Ziffer *)

   CASE ziffer OF
      |0: RETURN "0";
      |1: RETURN "1";
      |2: RETURN "2";
      |3: RETURN "3";
      |4: RETURN "4";
      |5: RETURN "5";
      |6: RETURN "6";
      |7: RETURN "7";
      |8: RETURN "8";
   END (* CASE ziffer *);
END Ziffer (* Funktion *);

(* ------------------------------------------------------------------------------- *)
(* ------------------------------------------------------------------------------- *)

BEGIN (* MODULE Mine *)

IF reqtoolsBase # NIL THEN
   reqLib := reqtools;

ELSE (* IF reqtoolsBase *)
   IF reqBase # NIL THEN
      reqLib := req;

   ELSE (* IF reqBase *)
      reqLib := system;
   END (* IF reqBase *);
END (* IF reqtoolsBase *);

WITH intuiText DO
   frontPen := schwarz;
   backPen := grau;
   drawMode := jam2;
   iTextFont := ADR (fontAttr);
   nextText := NIL;
END (* WITH intuiText *);

Assert (kickVersion >= 36, ADR (kick20));

screenPtr := NIL;
windowPtr := NIL;
windowPtr2 := NIL;
visualInfo := NIL;

DateStamp (ADR (date));
PutSeed (Eor (date.days, Eor (date.minute, date.tick)));

(* mein Font *)

WITH fontAttr DO
   name := ADR (fontName);
   ySize := fontHoehe;
   style := FontStyleSet {};
   flags := FontFlagSet {};
END (* WITH fontAttr *);

fontPtr := OpenDiskFont (ADR (fontAttr));

spielfeldMax := 10;

screenPtr := LockPubScreen (NIL);

LOOP
   ende := FALSE;
   anzNummer := 0;

   WITH zeit DO
      ticks := 0;
      minuten := 0;
      sekunden := 0;

      WITH zeitIText DO
         frontPen := schwarz;
         backPen := grau;
         drawMode := jam2;
         leftEdge := 0;
         topEdge := -1;
         iTextFont := ADR (fontAttr);
         iText := ADR (zeitStr);
         nextText := NIL;
      END (* WITH zeitIText *);
   END (* WITH Zeit *);

   (* das Anfangs-Fenster *)

   spielFeldMax := MaxAnzahlFelder (screenPtr);

   IF spielFeldMax > 77 THEN
      spielFeldMax := 77;
   END (* IF spielFeldMax *);

   innerWidth := 180;

   windowPtr := OpenWindowTagList (NIL, TAG (tagList,
                   waInnerHeight, 90,
                   waInnerWidth , innerWidth,
                   waLeft       , (LONGINT (screenPtr^.width) - 204) DIV 2,
                   waTop        , (LONGINT (screenPtr^.height) - 70) DIV 2,
                   waTitle      , ADR (mineStr),
                   waFlags      , CAST (LONGINT, WindowFlagSet {windowDrag,
                                                                windowClose,
                                                                gimmeZeroZero,
                                                                reportMouse,
                                                                windowDepth}),
                   waIDCMP      , CAST (LONGINT, IDCMPFlagSet {closeWindow} +
                                                 buttonIDCMP +
                                                 sliderIDCMP),
                   waPubScreen  , screenPtr,
                   tagEnd       , 0));

   Assert (windowPtr # NIL, ADR (openWindowError));

   IF visualInfo = NIL THEN
      visualInfo := GetVisualInfoA (windowPtr^.wScreen, NIL);
   END (* IF visualInfo *);

   gList := NIL;

   contextPtr := CreateContext (gList); (* bereitet gList für GadTools vor *)

   (* contextPtr und gList zeigen jetzt auf den selben Speicherbereich *)

   string80 := "5                ";

   ValToStr (spielFeldMax, FALSE, string2, 10, 2, "0", err);
   Concat (string80, string2);

   IF gList # NIL THEN
      (* der Slider *)

      WITH newGadget DO
         width := 154;
         height := 12;
         leftEdge := (innerWidth - width) DIV 2;
         topEdge := 18;
         gadgetText := ADR (string80);
         textAttr := ADR (fontAttr);
         gadgetID := 0;
         flags := NewGadgetFlagSet {placetextBelow};
         userData := NIL;
      END (* WITH newGadget *);

      newGadget.visualInfo := visualInfo;

      gadgetPtr := CreateGadgetA (sliderKind, gList^, newGadget, TAG (tagList,
                      gtslMin        , 5,
                      gtslMax        , spielFeldMax,
                      gtslLevel      , spielfeldMax,
                      gtslMaxLevelLen, 21,
                      tagEnd         , 0));

     DrawBevelBoxA (windowPtr^.rPort, newGadget.leftEdge,
         newGadget.topEdge, newGadget.width, newGadget.height,
         TAG (tagList, gtVisualInfo, visualInfo, tagEnd, 0));

      propInfoPtr := gadgetPtr^.specialInfo;

      (* weiter *)

      WITH newGadget DO
         width := 80;
         height := 12;
         leftEdge := (innerWidth - width) DIV 2;
         topEdge := 45;
         gadgetText := ADR (weiter);
         textAttr := ADR (fontAttr);
         gadgetID := 1;
         flags := NewGadgetFlagSet {placetextIn};
         userData := NIL;
      END (* WITH newGadget *);

      newGadget.visualInfo := visualInfo;

      gadgetPtr := CreateGadgetA (buttonKind, gadgetPtr^, newGadget, NIL);

      (* Infos und Hilfe *)

      WITH newGadget DO
         width := 128;
         height := 12;
         leftEdge := (innerWidth - width) DIV 2;
         topEdge := 60;
         gadgetText := ADR (info);
         textAttr := ADR (fontAttr);
         gadgetID := 2;
         flags := NewGadgetFlagSet {placetextIn};
         userData := NIL;
      END (* WITH newGadget *);

      newGadget.visualInfo := visualInfo;

      gadgetPtr := CreateGadgetA (buttonKind, gadgetPtr^, newGadget, NIL);

      (* Ruhmeshalle ansehen *)

      WITH newGadget DO
         width := 160;
         height := 12;
         leftEdge := (innerWidth - width) DIV 2;
         topEdge := 75;
         gadgetText := ADR (ruhmeshalleGadgetName);
         textAttr :=ADR (fontAttr);
         gadgetID := 3;
         flags := NewGadgetFlagSet {placetextIn};
         userData := NIL;
      END (* WITH newGadget *);

      newGadget.visualInfo := visualInfo;

      gadgetPtr := CreateGadgetA (buttonKind, gadgetPtr^, newGadget, NIL);

      i := AddGList (windowPtr, gList, 0, -1, NIL);
      RefreshGadgets (gList, windowPtr, NIL);
   END (* IF gList *);

   i := 0;

   intuiText.leftEdge := 17;
   intuiText.topEdge := 0;
   intuiText.iText := ADR (string80);
   string80 := seitenlaenge;
   PrintIText (windowPtr^.rPort, ADR (intuiText), newGadget.leftEdge, 7);

   intuiText.iText := ADR (string2);
   ValToStr (spielfeldMax, FALSE, string2, 10, 2, " ", err);
   PrintIText (windowPtr^.rPort, ADR (intuiText), newGadget.leftEdge + 104, 7);

   REPEAT
      WaitPort (windowPtr^.userPort);
      nachricht := GetMsg (windowPtr^.userPort);

      IF nachricht # NIL THEN
         flags := nachricht^.class;
         gadgetPtr := nachricht^.iAddress;
         ReplyMsg (nachricht);

         IF closeWindow IN flags THEN
            EXIT (* LOOP *);

         ELSIF gadgetUp IN flags THEN
            i := gadgetPtr^.gadgetID;

            IF i = 2 THEN
               Info (windowPtr);
            END (* IF i *);

            IF i = 3 THEN
               ValToStr (spielfeldMax, FALSE, string2, 10, 2, " ", err);

               Copy (string30, ruhmeshalleName);
               Concat (string30, string2);

               LadeRuhmeshalle (ruhmeshalle, string30);

               innerWidth := 276;
               innerHeight := 124;

               Copy (string30, ruhmeshalleFensterName);
               Concat (string30, string2);

               windowPtr2 := OpenWindowTagList (NIL, TAG (tagList,
                  waTitle      , ADR (string30),
                  waInnerWidth , innerWidth,
                  waInnerHeight, innerHeight,
                  waLeft       , (screenPtr^.width - innerWidth) DIV 2,
                  waTop        , (screenPtr^.height - innerHeight) DIV 2,
                  waFlags      , CAST (LONGINT, WindowFlagSet {windowClose,
                                                               windowDrag,
                                                               windowDepth,
                                                               gimmeZeroZero}),
                  waIDCMP      , CAST (LONGINT, IDCMPFlagSet {closeWindow}),
                  tagEnd       , 0));

               Assert (windowPtr2 # NIL, ADR (openWindowError));

               (* windowPtr^ und WindowPtr2^ sind auf dem selben Screen, also *)
               (* darf ich auch das selbe visualInfo verwenden *)

               DrawBevelBoxA (windowPtr2^.rPort, 20, 7, 234, 108, TAG (tagList,
                  gtbbRecessed, 0,
                  gtVisualInfo, visualInfo,
                  tagEnd      , 0));

               WITH intuiText DO
                  leftEdge := 0;
                  topEdge := 12;
               END (* WITH intuiText *);

               FOR i := 1 TO 10 DO
                  IF ruhmeshalle [i].anzM = AnzMinen (spielfeldMax) THEN
                     intuiText.frontPen := blau;
                  END (* IF ruhmeshalle *);

                  intuiText.iText := ADR (ruhmeshalle [i].name);
                  PrintIText (windowPtr2^.rPort, ADR (intuiText), 34, 10 * (i - 1));

                  intuiText.frontPen := schwarz;
               END (* FOR i *);

               flags := IDCMPFlagSet {};

               REPEAT
                  WaitPort (windowPtr2^.userPort);
                  nachricht := GetMsg (windowPtr2^.userPort);

                  IF nachricht # NIL THEN
                     flags := nachricht^.class;
                     ReplyMsg (nachricht);
                  END (* IF nachricht *);
               UNTIL closeWindow IN flags;

               CloseWindow (windowPtr2);
               windowPtr2 := NIL;
            END (* IF i *);
         END (* IF closeWindow *);

         spielfeldMax := 5 + (propInfoPtr^.horizPot DIV propInfoPtr^.horizBody);

         IF spielfeldMax = spielFeldMax + 1 THEN
            spielfeldMax := spielFeldMax;
         END (* IF spielfeldMax *);

         intuiText.leftEdge := 17;
         intuiText.topEdge := 0;
         intuiText.iText := ADR (string2);

         ValToStr (spielfeldMax, FALSE, string2, 10, 2, "0", err);
         PrintIText (windowPtr^.rPort, ADR (intuiText),
            newGadget.leftEdge + 104, 7);
      END (* IF nachricht *);
   UNTIL i = 1;

   FreeVisualInfo (visualInfo);
   visualInfo := NIL;

   i := RemoveGList (windowPtr, gList, 5);

   FreeGadgets (gList);
   gList := NIL;

   CloseWindow (windowPtr);
   windowPtr := NIL;

   anzMinen := AnzMinen (spielfeldMax);

   (* spielfeldMax <= 44 (PAL SuperHires-Interlace mit vollem Overscan) *)
   (* ==>  anzMinen <= 322 *)

   anzFahnen := 0;

   (* Das eigentliche Spiel-Fenster *)

   string2 := "";

   VerteileMinen (spielfeld, spielfeldMax, anzMinen);

   innerWidth := LONGINT ((spielfeldMax * boxX) + (4 * randBreite) + spielfeldMax -
      1);
   innerHeight := LONGINT ((spielfeldMax * boxY) + (2 * randBreite) - 2 + 10);

   windowPtr := OpenWindowTagList (NIL, TAG (tagList,
      waTitle      , ADR (mineStr),
      waInnerWidth , innerWidth,
      waInnerHeight, innerHeight,
      waFlags      , CAST (LONGINT, WindowFlagSet {windowDrag, windowDepth,
                        windowClose, gimmeZeroZero, rmbTrap}),
      waIDCMP      , CAST (LONGINT, IDCMPFlagSet {mouseButtons, closeWindow,
                        intuiTicks}),
      waLeft       , (screenPtr^.width - innerWidth) DIV 2,
      waTop        , (screenPtr^.height - innerHeight) DIV 2,
      waPubScreen  , screenPtr,
      tagEnd       , 0));

   Assert (windowPtr # NIL, ADR (openWindowError));

   InitImages ();

   (* Felder zeichnen *)

   FOR i := 1 TO spielfeldMax DO (* Zeilen *)
      FOR j := 1 TO spielfeldMax DO (* Spalten *)
         DrawImage (windowPtr^.rPort, ADR (clickmeImage),
            2 * randBreite + (j - 1) * clickmeImage.width + horoffset * (j - 1),
            randBreite + (i - 1) * clickmeImage.height);
      END (* FOR j *);
   END (* FOR i *);

   Copy (zeit.zeitStr, zeitS);
   Concat (zeit.zeitStr, "00:00");

   PrintIText (windowPtr^.rPort, ADR (zeit.zeitIText),
      (windowPtr^.gzzWidth - 88) DIV 2, 2 * randBreite + spielfeldMax * boxY);

   (* Aktion des Spielers abwarten und reagieren *)

   flags := IDCMPFlagSet {};

   REPEAT
      WaitPort (windowPtr^.userPort);
      nachricht := GetMsg (windowPtr^.userPort);

      IF nachricht # NIL THEN
         flags := nachricht^.class;
         qualifiers := nachricht^.qualifier;

         mausX := windowPtr^.gzzMouseX;
         mausY := windowPtr^.gzzMouseY;

         ReplyMsg (nachricht);

         (* so, jetzt geht's ans Auswerten *)

         IF intuiTicks IN flags THEN
            IF zeit.ticks < 9 THEN
               INC (zeit.ticks);

            ELSE (* IF zeit.ticks = 9 *)
               zeit.ticks := 0;

               IF zeit.sekunden < 59 THEN
                  INC (zeit.sekunden);

               ELSE (* IF zeit.sekunden *)
                  zeit.sekunden := 0;

                  IF zeit.minuten < 99 THEN
                     INC (zeit.minuten);

                  ELSE (* IF zeit.minuten *)
                     ende := TRUE;
                  END (* IF zeit.minuten *);
               END (* IF zeit.sekunden *);

               IF NOT ende THEN
                  WITH zeit DO
                     Copy (zeitStr, zeitS);

                     ValToStr (minuten, FALSE, hilfeStr, 10, 2, "0", err);
                     Concat (zeitStr, hilfeStr);

                     ConcatChar (zeitStr, ":");

                     ValToStr (sekunden, FALSE, hilfeStr, 10, 2, "0", err);
                     Concat (zeitStr, hilfeStr);

                     PrintIText (windowPtr^.rPort, ADR (zeitIText),
                        (windowPtr^.gzzWidth - 88) DIV 2,
                        2 * randBreite + spielfeldMax * boxY);
                  END (* WITH Zeit *);
               END (* IF NOT ende *);
            END (* IF zeit.ticks *);

         ELSIF mouseButtons IN flags THEN
            i := ((mausY - randBreite) DIV boxY) + 1; (* Zeile *)
            j := ((mausX - 2 * randBreite) DIV (boxX + horoffset)) + 1;

            IF (i >= 1) AND (i <= spielfeldMax) AND (j >= 1) AND
               (j <= spielfeldMax) THEN
               (* das Feld [i, j] angeklickt *)
               IF rightButton IN qualifiers THEN
                  IF spielfeld [i, j] >= mine THEN
                     IF anzFahnen < anzMinen THEN
                        DrawImage (windowPtr^.rPort, ADR (fahneImage),
                           2 * randBreite + (j - 1) * fahneImage.width +
                              horoffset * (j - 1),
                           randBreite + (i - 1) * fahneImage.height);

                        spielfeld [i, j] := spielfeld [i, j] + fahne;

                        INC (anzFahnen);
                     END (* IF anzFahnen *);

                  ELSE (* IF spielfeld [i, j] >= mine *)
                     IF spielfeld [i, j] <= fahne + 8 THEN
                        DrawImage (windowPtr^.rPort, ADR (clickmeImage),
                           2 * randBreite + (j - 1) * clickmeImage.width +
                              horoffset * (j - 1),
                           randBreite + (i - 1) * clickmeImage.height);

                        spielfeld [i, j] := spielfeld [i, j] - fahne;

                        DEC (anzFahnen);
                     END (* IF spielfeld [i, j] = fahne *);
                  END (* IF spielfeld [i, j] >= mine *);

               ELSIF leftButton IN qualifiers THEN
                  intuiText.leftEdge := 8;
                  intuiText.topEdge := 2;
                  intuiText.iText := ADR (string2);

                  IF spielfeld [i, j] >= mine THEN
                     IF spielfeld [i, j] # mine THEN
                        DrawImage (windowPtr^.rPort, ADR (clickedImage),
                           2 * randBreite + (j - 1) * clickedImage.width +
                              horoffset * (j - 1),
                           randBreite + (i - 1) * clickedImage.height);
                         string2 [0] := Ziffer (spielfeld [i, j]);

                         PrintIText (windowPtr^.rPort, ADR (intuiText),
                           2 * randBreite + (j - 1) * clickedImage.width +
                              horoffset * (j - 1),
                           randBreite + (i - 1) * clickedImage.height);

                        spielfeld [i, j] := nummer;
                        INC (anzNummer);

                     ELSE (* IF # mine *)
                        DrawImage (windowPtr^.rPort, ADR (explosionImage),
                           2 * randBreite + (j - 1) * explosionImage.width +
                           horoffset * (j - 1),
                           randBreite + (i - 1) * explosionImage.height);

                        Beep (booob, infoPri);

                        ende := TRUE;
                     END (* IF # mine *);
                  END (*IF >= mine *);
               END (* IF rightButton *);
            END (* IF (i >= 1 *);
         END (* IF mouseButtons *);
      END (* IF nachricht # NIL *);
   UNTIL ende OR (closeWindow IN flags) OR
      ((spielfeldMax * spielfeldMax - anzFahnen - anzNummer) = 0);

   IF closeWindow IN flags THEN
      CloseWindow (windowPtr);
      windowPtr := NIL;

      EXIT (* LOOP *);
   END (* IF closeWindow *);

   ModifyIDCMP (windowPtr, IDCMPFlagSet {}); (* keine Nachrichten mehr! *)

   (* Anzahl der gefundenen Minen zählen *)

   anzMinen := 0;

   FOR i := 1 TO spielfeldMax DO
      FOR j := 1 TO spielfeldMax DO
         IF spielfeld [i, j] = fahne + mine THEN
            INC (anzMinen);

            (* Bombe zeichnen *)

            DrawImage (windowPtr^.rPort, ADR (bombeImage),
               2 * randBreite + (j - 1) * clickedImage.width + horoffset * (j - 1),
               randBreite + (i - 1) * clickedImage.height);
         END (* IF spielfeld *);
      END (* FOR j *);
   END (* FOR i *);

   IF ((spielfeldMax * spielfeldMax) - anzNummer - anzMinen) = 0 THEN
      (* alles abgeräumt! *)
      Beep (beeeb, infoPri);
   END (* IF ((spielfeldMax *);

   ValToStr (spielfeldMax, FALSE, string2, 10, 2, " ", err);

   Copy (string30, ruhmeshalleName);
   Concat (string30, string2);

   LadeRuhmeshalle (ruhmeshalle, string30);

   eintrag.anzM := anzMinen;
   eintrag.zeit.min := zeit.minuten;
   eintrag.zeit.sec := zeit.sekunden;

   pos := Pos (ruhmeshalle, eintrag);

   IF pos <= 10 THEN (* drin! *)
      j := StringRequest (eintrag.name, 17, 16, stringReqTitle, windowPtr,
              reqLib);

      IF j = noError THEN
         FuegeEintragEin (ruhmeshalle, eintrag, pos);
         SaveRuhmeshalle (ruhmeshalle, string30);
      END (* IF noError *);

   ELSE (* IF pos *)
      IF (spielfeldMax * spielfeldMax - anzFahnen - anzNummer) # 0 THEN
         flags := IDCMPFlagSet {};
         j := TextRequest (keinRuhm, NIL, weiter, "Mine: \"BANG!\"", reqLib, flags,
                 windowPtr);
      END (* IF (spielfeldMax *);
   END (* IF pos *);

   CloseWindow (windowPtr);
   windowPtr := NIL;

   (* Ruhmeshalle anzeigen *);

   Copy (string80, ruhmeshalleFensterName);
   Concat (string80, string2);

   innerWidth := 276;
   innerHeight := 124;

   windowPtr := OpenWindowTagList (NIL, TAG (tagList,
      waTitle      , ADR (string80),
      waInnerWidth , innerWidth,
      waInnerHeight, innerHeight,
      waLeft       , (screenPtr^.width - innerWidth) DIV 2,
      waTop        , (screenPtr^.height - innerHeight) DIV 2,
      waFlags      , CAST (LONGINT, WindowFlagSet {windowClose,
                                                   windowDrag,
                                                   windowDepth,
                                                   gimmeZeroZero}),
      waIDCMP      , CAST (LONGINT, IDCMPFlagSet {closeWindow}),
      tagEnd       , 0));

   Assert (windowPtr # NIL, ADR (openWindowError));

   IF visualInfo = NIL THEN
      visualInfo := GetVisualInfoA (windowPtr^.wScreen, NIL);
   END (* IF visualInfo *);

   DrawBevelBoxA (windowPtr^.rPort, 20, 7, 234, 108, TAG (tagList,
      gtbbRecessed, 0,
      gtVisualInfo, visualInfo,
      tagEnd      , 0));

   FreeVisualInfo (visualInfo);
   visualInfo := NIL;

   WITH intuiText DO
      leftEdge := 0;
      topEdge := 12;
   END (* WITH intuiText *);

   FOR i := 1 TO 10 DO
      intuiText.iText := ADR (ruhmeshalle [i].name);

      IF ruhmeshalle [i].anzM = AnzMinen (spielfeldMax) THEN
         intuiText.frontPen := blau;
      END (* IF ruhmeshalle *);

      IF (i = pos) AND (j = noError) THEN
         intuiText.frontPen := weiss;
      END (* IF i *);

      PrintIText (windowPtr^.rPort, ADR (intuiText), 34, 10 * (i - 1));

      intuiText.frontPen := schwarz;
   END (* FOR i *);

   flags := IDCMPFlagSet {};

   REPEAT
      WaitPort (windowPtr^.userPort);
      nachricht := GetMsg (windowPtr^.userPort);

      IF nachricht # NIL THEN
         flags := nachricht^.class;
         ReplyMsg (nachricht);
      END (* IF nachricht *);
   UNTIL closeWindow IN flags;

   CloseWindow (windowPtr);
   windowPtr := NIL;
END (* LOOP *);

(* ------------------------------------------------------------------------------- *)

CLOSE;

IF screenPtr # NIL THEN
   UnlockPubScreen (NIL, screenPtr);
   screenPtr := NIL;
END (* IF screenPtr *);

IF visualInfo # NIL THEN
   FreeVisualInfo (visualInfo);
   visualInfo := NIL;
END (* IF visualInfo *);

IF windowPtr # NIL THEN
   IF gList # NIL THEN
      i := RemoveGList (windowPtr, gList, 5);
      FreeGadgets (gList);
      gList := NIL;
   END (* IF gList *);

   CloseWindow (windowPtr);
   windowPtr := NIL;
END (* IF windowPtr *);

IF windowPtr2 # NIL THEN
   CloseWindow (windowPtr2);
   windowPtr2 := NIL;
END (* IF windowPtr2 *);

IF fontPtr # NIL THEN
   CloseFont (fontPtr);
   fontPtr := NIL;
END (* IF fontPtr *);

END Mine (* MODUL *).