MODULE Hanoi;

(* ------------------------ *)
(* (c) by Roger Meyer, 1992 *)
(* ------------------------ *)

IMPORT
  G : DHanoi,              (* Displayroutinen      *)
  S : Stacks,              (* Stack-Verwaltung     *)
  E : Exec,                (* OS: Task/Mem-Manager *)
  D : Dos,                 (* OS: Dos              *)
      NoGuruRq;            (* Oberon: verhindert   *)
                           (*      Systemabstuerze *)

CONST
  Depth   = 9;                        (* Anzahl Scheiben                    *)

TYPE
  DiskPtr = POINTER TO Disk;          (* Pointertyp                         *)
  Disk    = RECORD(S.Node);           (* Disk: vererbt Eigenschaften von    *)
              Number : INTEGER;       (* Stacks.Node.                       *)
            END;                      (* Number: Breite der Scheibe         *)

VAR
  Tower : ARRAY 3 OF S.StackPtr;      (* Drei Tuerme                        *)
  DS    : INTEGER;                    (* Dummy INTEGER                      *)


CONST
  Height    = 1;                      (* Hoehe der Scheiben = Height+1 Pkte *)

  HStep     = Height+2;               (* Zwischenraum = 1 Pkt               *)
  Step      = Height*2+2;             (* Breitenzuwachs pro Scheibe         *)
  VStep     = 2*(Depth+2)*Step;       (* Zwischenraum Stick-Stick           *)
  Base      = Depth*HStep+10;         (* Basislinie                         *)
  WinWidth  = VStep*3+4*Step;         (* Breite der Ausgabe                 *)
  WinHeight = Base+20;                (* Hoehe der Ausgabe                  *)

  StOne     = VStep DIV 2;            (* Standort erster Stick              *)
  StTwo     = StOne+VStep;            (*     "    zweiter  "                *)
  StThree   = StOne+VStep*2;          (*     "    dritter  "                *)


PROCEDURE Display;                    (* Ausgabe der aktuellen Situation    *)

PROCEDURE DisplayStick(Stick : INTEGER);    (* Ausgabe eines Sticks         *)

VAR
  N      : S.NodePtr;                 (* N: Zwischenspeicher                *)
  Z,C    : INTEGER;                   (* Z: Anz Scheiben auf einem Stick    *)
                                      (* C: Counter                         *)
  x,y,h  : INTEGER;                   (* x: X-Pos des Scheibenlochs         *)
                                      (* y: Y-Pos des Scheibenlochs         *)
                                      (* h: Radius der Scheibe              *)
  Centre : INTEGER;                   (* Centre: Mitte des Sticks           *)

BEGIN
  CASE Stick OF                       (* Standort des Sticks ermitteln      *)
    0 : Centre := StOne;              (* |                                  *)
  | 1 : Centre := StTwo;              (* |                                  *)
  | 2 : Centre := StThree;            (* |                                  *)
  END;                                (* |                                  *)

  Z := S.NumberOfNodes(Tower[Stick]); (* Anzahl der Scheiben auf dem Stick  *)
  C := 0;                             (* Zaehler initialisieren             *)

  WHILE C<Z DO                        (** Schleife 1 **)

    N := S.GetNodeNum(Tower[Stick],Z-(C+1)); (* Node Nummer Z-(C+1) merken  *)
    h := N^(Disk).Number*Step;        (* Lok. Type Guard: Groesse des Stick *)
                                      (* auslesen und Breite berechnen      *)
    y := Base-C*HStep;                (* Hoehe ueber der Basis berechnen    *)
    G.FillRectangle(Centre-h,y-Height,Centre+h,y); (* Scheibe zeichnen      *)
    INC(C);                           (* Zaehler erhoehen                   *)

  END;                                (** Ende Schleife 1, wenn letzte **)
                                      (** Scheibe dargestellt          **)

END DisplayStick;

BEGIN
  G.ClearScreen();                    (* Bild loeschen                      *)

  DisplayStick(0);                    (* Sticks darstellen                  *)
  DisplayStick(1);                    (* |                                  *)
  DisplayStick(2);                    (* |                                  *)

  D.Delay(5);                         (* 5/50 sec warten                    *)

END Display;


PROCEDURE InitTower;                  (* Programm Initialisieren            *)

VAR
  Count   : INTEGER;                  (* Zaehler                            *)
  TheDisk : DiskPtr;                  (* Temporaere Scheibe                 *)

BEGIN
  Tower[0] := S.NewStack();           (* Stacks neu anlegen                 *)
  Tower[1] := S.NewStack();           (* |                                  *)
  Tower[2] := S.NewStack();           (* |                                  *)

  Count := 0;                         (* Counter initialisieren             *)

  WHILE Count<Depth DO                (** Schleife 1 **)

    NEW(TheDisk);                     (* Neue Scheibe                       *)
    TheDisk.Number := Depth-Count;    (* Groesse setzen                     *)
    S.Put(Tower[0],TheDisk);          (* Auf den Stack legen                *)
    INC(Count);                       (* Counter erhoehen                   *)

  END;                                (** Ende Schleife 1, wenn letzte     **)
                                      (** Scheibe initialisiert            **)

  G.SetGraphMode(WinWidth,WinHeight); (* Ausgabe initialisieren             *)

END InitTower;


PROCEDURE CleanUp;                    (* Aufraeumen                         *)

BEGIN
  S.DisposeStack(Tower[0]);           (* Stacks loeschen                    *)
  S.DisposeStack(Tower[1]);           (* |                                  *)
  S.DisposeStack(Tower[2]);           (* GarbageCollector fuer die Scheiben *)

  G.EndGraphMode;                     (* Graphik abschalten                 *)

END CleanUp;


PROCEDURE WhichStack(from,to : INTEGER):INTEGER; (* Stack ermitteln         *)

VAR
  S : SET;

BEGIN
  S := -{from,to};                    (* Umkehrmenge von {from,to}          *)
  IF 0 IN S THEN RETURN 0 END;        (* Uebrig bleibt der Stack fuer die   *)
  IF 1 IN S THEN RETURN 1 END;        (* die Verschiebung                   *)
  IF 2 IN S THEN RETURN 2 END;        (* |                                  *)

END WhichStack;


PROCEDURE Move(from,to,anz : INTEGER); (* Scheiben von from nach to         *)
                                       (* verschieben                       *)

VAR
  TheStack : INTEGER;                  (* Nummer des Zwischen-Stacks        *)

BEGIN
  CASE anz OF                          (* Wenn ...                          *)
    0 : RETURN;                        (* ... anz=0 => mache nichts         *)
                                       (*     (Wir sind am Boden)           *)
  | 1 : S.Put(Tower[to],S.Get(Tower[from]));
                                       (* ... anz=1 => Scheibe verschieben  *)
        Display;                       (*     und Situation ausgeben        *)
  ELSE
    TheStack := WhichStack(from,to);   (* ... sonst => Ermittle 'freier'    *)
                                       (*     Stack                         *)
    Move(from,TheStack,anz-1);         (*     Verschiebe alle ausser den    *)
                                       (*     letzten auf den ZwSpeicher    *)
    Move(from,to,1);                   (*     Groesste Scheibe verschieben  *)
    Move(TheStack,to,anz-1);           (*     ZwSpeicher auf das Ziel       *)

  END;

END Move;


BEGIN
  InitTower;                           (* 'Towers' initialisieren           *)
  DS := E.SetTaskPri(E.FindTask(NIL),-5);
                                       (* OS: TaskPrioritaet erniedrigeren  *)
  Move(0,2,Depth);                     (* Versch. alle Scheiben von 0 => 2  *)

CLOSE                                  (* Der folgende Code wird unter      *)
                                       (* allen Umstaenden ausgefuehrt      *)
  CleanUp;                             (* Spielzeug wieder einpacken        *)

END Hanoi.
