
{****************************************************************************}
{*                                                                          *}
{*                      Class Scheduling Program                            *}
{*                      ========================                            *}
{*                                                                          *}
{*     From a list of classes and sections, this program will generate all  *}
{*  possible options                                                        *}
{*                                                                          *}
{*                                                                          *}
{*                                                                          *}
{****************************************************************************}



PROGRAM Schedule (INPUT, OUTPUT);
  CONST
  	HOURS = 0;
        MINUTES = 1;
    { the gaps must be at least that large to be usefull }
    MinFree  = 120;            { 2 hours (in minutes) }
    { when the sum of the gaps reach that value the schedule is 'bad' }
    MaxGap   = 240;            { 4 hours (in minutes) }
    FF       = 12;             { form feed character }
    Stars    =                 { used for the printout procedure }
'**********************************************************************';

  TYPE
    String4  = STRING[4];
    String6  = STRING[6];
    String7  = STRING[7];
    String8  = STRING[8];
    String11 = STRING[11];
    String12 = STRING[12];
    String40 = STRING[40];
    String80 = STRING[80];
    String255= STRING[255];
    TimeList = ^TimeRec;
    TimeRec  = RECORD
                 Day : INTEGER;
                 T1  : INTEGER;
                 T2  : INTEGER;
                 Next: TimeList;
               END;
    DataList = ^DataRec;
    DataRec  = RECORD
                 Name    : String8;
                 Section : String4;
                 Extra   : String80;
                 InSched : BOOLEAN;
                 Times   : TimeList;
                 Next    : DataList
               END;
    ClasList = ^ClasRec;
    ClasRec  = RECORD
                 Info    : DataList;
                 Used    : BOOLEAN;
                 T1      : INTEGER;
                 T2      : INTEGER;
                 Prev    : ClasList;
                 Next    : ClasList
               END;
    ClasArr  = ARRAY[0..4] OF ClasList;
    DataArr  = ARRAY[0..19] OF DataList;

  VAR
    Last_line: String255;
    Earliest, Latest : String255;
    First, Last, I, SchedNum, OptNum, Max, Code : INTEGER;
    NoOutput, Normal, Ok : BOOLEAN;
    Sched : ClasArr;
    Data  : DataArr;
    Ptr   : DataList;
    Time  : TimeList;
    Ch    : CHAR;
    MaxStr, EarlyDef, LateDef : String6;
    FileIn, FileOut : String12;
    InFile, OutFile : TEXT;
{ -------------------------------------------------------- }
PROCEDURE Error(S : String255; Msg : String255);
VAR
   x, i : INTEGER;
BEGIN
	i := LENGTH(S) - 2;
	WRITELN('Error on line:');
	WRITELN(Last_line);
	WRITE('at: ');
	FOR x := 4 TO (LENGTH(Last_line) - i) DO
		WRITE(' ');
        WRITELN(Copy(S, 1, i));
        WRITELN('Error type: ', Msg);
        HALT;
END;

{ get rid of tabs }
FUNCTION FIX_TABS(S : String255) : String255;
VAR
   i, len : INTEGER;	
BEGIN
   i := 1;
   len := LENGTH(S);
   WHILE (i <= len) DO BEGIN
         IF S[i] = ^I THEN
            S[i] := ' ';
         i := i + 1;
   END;
   FIX_TABS := S;
END;
{ -------------------------------------------------------- }

  FUNCTION Ltrim(S : String255) : String255;
    VAR
      I : INTEGER;
  BEGIN { Ltrim }
    I := 1;
    WHILE S[I] = ' ' DO I := I + 1;
    Ltrim := Copy (S, I, 255)
  END;  { Ltrim }

{============================================================================}
{=  This procedure converts the head of the string S into a the a number    =}
{=  The head of the string must have the format:                            =}
{=      HH:MMxx  for example  9:30am  or 12:00pm                            =}
{=  The integer value returned is the time in minutes.                      =} 
{============================================================================} 
  PROCEDURE ConvertTime(S : String255; VAR T : INTEGER);
    VAR
      Hours, Minutes, i : INTEGER;
      AmPm, Dummy : CHAR;
        { ---------------------------------------------------}
        PROCEDURE Check_Time(S : STring255; Kind : INTEGER);
        VAR
                i, error_code : INTEGER;
        BEGIN
        IF Kind = HOURS THEN BEGIN
                Val(Copy(S, 1, Pos(':', S) - 1), i, error_code);
                IF error_code <> 0 THEN
                        Error(S, 'not a number');
                IF i IN [1..12] THEN
                        EXIT;
                Error(S, 'Hours must be 1 to 12.');
        END
        ELSE BEGIN
                { check if second digit is a number.  users can type
                  1:0, and it will be ok. }
                Val(Copy(S, 2, 2), i, error_code);
                IF error_code = 0 THEN
                        Val(Copy(S, 1, 2), i, error_code)
                ELSE
                        Val(Copy(S, 1, 1), i, error_code);
                IF error_code <> 0 THEN
                        Error(S, 'not a number');
                IF i IN [0..59] THEN
                        EXIT;
                Error(S, 'Minutes must be 0 to 59.');
        END;
        END;
        { ---------------------------------------------------}

  BEGIN { ConvertTime }

    Check_Time(S, HOURS);
    Val(Copy(S, 1, Pos(':', S) - 1), Hours, I);
    S := Copy(S, Pos(':', S) + 1, 255);
    Check_Time(S, MINUTES);
        { check for ambiguous input }
        Val(Copy(S, 1, 2), Minutes, I);
        IF I = 0 THEN BEGIN         { both character are digits }
                Val(Copy(S, 1, 2), Minutes, I);
                S := Copy(S, 3, 255);
        END
        ELSE BEGIN                  { only the first is. }
                Val(Copy(S, 1, 1), Minutes, I);
                S := Copy(S, 2, 255);
        END;
    AmPm := S[1];
    { check what this really was... }
    IF NOT (AmPm IN ['a', 'A', 'p', 'P', ' ', '-']) THEN
        Error(S, 'You need an `am'' or a `pm'', or space ` '' or dash `-''.');
    IF (AmPm IN ['p','P']) AND (Hours <> 12) THEN Hours := Hours + 12;
    T := Hours * 60 + Minutes;
  END;  { ConvertTime }

{============================================================================}
{=  This procedure reads all the days and times of a section and stores the =}
{=  information in a TimeList.                                              =}
{============================================================================}
  PROCEDURE GetHours(S : String255; DataPtr : DataList);
    VAR
      Day, T1, T2, Count : INTEGER;
      Time,Ptr,Ptr1: TimeList;
      Day_error : BOOLEAN;

  BEGIN { GetHours }
    Time :=  NIL;
    S := LTRIM(S);
    Day_error := TRUE;

    WHILE S[1] IN ['M','T','W','F'] DO BEGIN { Loop as long it is a day code }
      Day_error := FALSE;
      Ptr1 := Time;
      { Start by getting all the days of a section with the same hours }
      { and store the day code into the list. }
      WHILE S[1] IN ['M','T','W','F'] DO BEGIN
        NEW(Ptr); { make a new entry for that day }
        Ptr^.Next := Time;
        Count := 1; { dirty trick that tells me if the day is 1 or 2 char long }
        { Convert the day into an integer code stored in the list }
        CASE S[1] OF
          'M' : Ptr^.Day := 0;
          'W' : Ptr^.Day := 2;
          'F' : Ptr^.Day := 4;
          'T' : BEGIN
                  Count := 2; { Here we are, those are 2 character long days }
                  IF S[2] IN ['u','U'] THEN Ptr^.Day := 1
                  ELSE IF S[2] IN ['h','H'] THEN Ptr^.Day := 3
                  ELSE
                       ERROR(S, 'The day isn''t Tu or Th');
                END
        END;
        Time := Ptr; { add the new entry to the list }
        { scan the rest of the string }
        S := LTRIM(COPY(S, Count + 1, 255));
      END;

      { Now, we read all the days for a given time. We have to read the times }
      ConvertTime(S,T1);
      { skip up to next time info }

      S := LTRIM(COPY(S, POS('-', S) + 1, 255));
      ConvertTime(S, T2);
      { skip up to field deliminator }
      S := LTRIM(COPY(S, POS(' ', S), 255));
      { determine the extreme values for all the data }
      IF T1 < First THEN First := T1;
      IF T2 > Last THEN Last := T2;
      { traverse the whole list to store the time }
      {!!! this information is disgustingly redundant !!!}
      Ptr := Time;
      WHILE Ptr <> Ptr1 DO BEGIN
        Ptr^.T1 := T1;
        Ptr^.T2 := T2;
        Ptr := Ptr^.Next
      END;
    END;
    IF Day_error THEN
        Error(S, 'Did not find any days of the week.');
    DataPtr^.Extra := COPY(S, 1, LENGTH(S)-1);
    DataPtr^.Times := Time
  END;  { GetHours }

  {============================================================================}
  {============================================================================}
  PROCEDURE ReadData;
    VAR
      I : INTEGER;
      S : String255;
      Ptr, Ptr2 : DataList;
  BEGIN { ReadData }
    FOR I := 0 TO 19 DO Data[I] := NIL;
    First := 1440;
    Last := 0;
    I := 0;
    WHILE NOT EOF(InFile) DO BEGIN
      NEW(Ptr);
      READLN(InFile, S);
      S := FIX_TABS(S);			{ get rid of tabs }
      S := LTRIM(S);
      Last_line := Copy(S, 1, 255);	{ error msg line }
      S := S + '  X';
      Ptr^.Name := COPY(S, 1, POS(' ', S));
      S := LTRIM(COPY(S, POS(' ', S), 255));
      Ptr^.Section := COPY(S, 1, POS(' ', S));
      S := LTRIM(COPY(S, POS(' ', S), 255));
      GetHours(S, Ptr);
      Ptr^.Next := NIL;
      IF Data[I] = NIL THEN
        Data[I] := Ptr
      ELSE IF Data[I]^.Name <> Ptr^.Name THEN BEGIN
        I := I + 1;
        Data[I] := Ptr
      END
      ELSE BEGIN
        Ptr2 := Data[I];
        WHILE Ptr2^.Next <> NIL DO Ptr2 := Ptr2^.Next;
        Ptr2^.Next := Ptr
      END
    END;
    CLOSE(InFile)
  END;  { ReadData }

{============================================================================}
{=  When processing is finished with one person, this procedure erases      =}
{=  all the data structures used to keep information.                       =}
{============================================================================}
  PROCEDURE EraseInfo;
    VAR
      I : INTEGER;
      Ptr1, Temp1 : DataList;
      Ptr2, Temp2 : TimeList;
  BEGIN { EraseInfo }
    I := 0;
    WHILE Data[I] <> NIL DO BEGIN
      Ptr1 := Data[I];
      WHILE Ptr1 <> NIL DO BEGIN
        Ptr2 := Ptr1^.Times;
        WHILE Ptr2 <> NIL DO BEGIN
          Temp2 := Ptr2;
          Ptr2 := Ptr2^.Next;
          DISPOSE(Temp2)
        END;
        Temp1 := Ptr1;
        Ptr1 := Ptr1^.Next;
        DISPOSE(Temp1)
      END;
      I := I + 1
    END;
    FOR I := 0 TO 4 DO DISPOSE(Sched[I])
  END;  { EraseInfo }


{***********************************************************************}
{*                                                                     *}
{*                       Results printing                              *}
{*                                                                     *}
{***********************************************************************}

{============================================================================}
{=  This function determines how 'good' a schedule is.                      =}
{=  The way this is done is by summing up all the inter-class gaps that are =}
{=  shorter than the constant MinFree.  Then, if that sum is larger or      =}
{=  equal to the constant MaxGap, the schedule is 'bad'.                    =}
{============================================================================}
  FUNCTION Optimized : BOOLEAN;
    VAR
      Gap, I : INTEGER;
      Ptr : ClasList;
  BEGIN { Optimized }
    Gap := 0;
    FOR I := 0 TO 4 DO BEGIN
      Ptr := Sched[I];
      Ptr := Ptr^.Next;
      IF Ptr <> NIL THEN
        WHILE Ptr^.Next <> NIL DO BEGIN
          IF NOT Ptr^.Used THEN
            IF(Ptr^.T2-Ptr^.T1) < MinFree THEN Gap := Gap + Ptr^.T2-Ptr^.T1;
          Ptr := Ptr^.Next
        END
    END;
    IF Gap < MaxGap THEN OptNum := OptNum + 1;
    Optimized := (Gap < MaxGap) OR Normal
  END;  { Optimized }

{============================================================================}
{=  Converts an integer representing a time in minutes into a string        =}
{============================================================================}
  FUNCTION TimeStr(T : INTEGER): String7;
    VAR
      S : String7;
      S1  : String7;
      AmPm : CHAR;
  BEGIN { TimeStr }
    IF (T DIV 60) >= 12 THEN BEGIN
      IF (T DIV 60) <> 12 THEN T := T - 60 * 12;
      AmPm := 'p'
    END ELSE AmPm := 'a';
    Str(T DIV 60:2,S);
    Str(T MOD 60:2,S1);
    S := S + ':' + S1 + AmPm + 'm';
    IF (T MOD 60) = 0 THEN S[4] := '0';
    TimeStr := S
  END;  { TimeStr }

{============================================================================}
{=  Counts the number of schedules and prints them out if NoOutput is false =}
{=  Will print out only optimized schedules if Normal is false              =}
{=  NoOutput and Normal are global boolean variables                        =}
{============================================================================}
  PROCEDURE PrintOut(VAR  OutFile : TEXT);
    VAR
      Ch  : CHAR;
      Ptr1: DataList;
      Ptr : ClasList;
      DatOut : ARRAY[0..4,0..95] OF String11;
      Time, T, I, J : INTEGER;
  BEGIN { PrintOut }
    IF Optimized THEN BEGIN
      SchedNum := SchedNum + 1;
      IF NOT NoOutput THEN BEGIN
        FOR I := 0 TO 4 DO
          FOR J := 0 TO (Last-First) DIV 15 DO
            DatOut[I,J] := '           ';
        FOR I := 0 TO 4 DO BEGIN
          Ptr := Sched[I];
          WHILE Ptr <> NIL DO BEGIN
            IF Ptr^.Used THEN BEGIN
              DatOut[I,(Ptr^.T1-First) DIV 15] := 'ÄÄÄÄÄÄÄÄÄÄÄ';
              DatOut[I,(Ptr^.T1-First) DIV 15+1] :=
                 Ptr^.Info^.Name + ' ';
              DatOut[I,(Ptr^.T1-First) DIV 15+2] :=
                 Ptr^.Info^.Section + '   ';
              DatOut[I,(Ptr^.T2-First) DIV 15] := 'ÄÄÄÄÄÄÄÄÄÄÄ';
            END;
            Ptr := Ptr^.Next
          END
        END;
        IF FileOut = '' THEN Clrscr
        ELSE WRITELN(OutFile, CHR(FF));
        WRITELN(OutFile);
        WRITELN(OutFile, 'Attempt schedule ', SchedNum : 2);
        WRITELN(OutFile, 'ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ');
        WRITELN(OutFile);
        WRITELN(OutFile);
        WRITELN(OutFile, ' É':9,
                'ÍÍÍÍÍÍÍÍÍÍÍÑÍÍÍÍÍÍÍÍÍÍÍÑÍÍÍÍÍÍÍÍÍÍÍÑÍÍÍÍÍÍÍÍÍÍÍÑÍÍÍÍÍÍÍÍÍÍÍ»');
        WRITELN(OutFile, ' º':9, '³':12, '³':12, '³':12, '³':12, 'º':12);
        WRITELN(OutFile, ' º' : 9, '  MONDAY   ³  TUESDAY  ³ WEDNESDAY ',
                               '³ THURSDAY  ³  FRIDAY   º');
        WRITELN(OutFile, ' º':9, '³':12, '³':12, '³':12, '³':12, 'º':12);
        WRITELN(OutFile, ' Ì':9,
                'ÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍ¹');
        FOR J := 0 TO (Last-First) DIV 15 DO BEGIN
          IF (J MOD 2) = 0 THEN
            WRITE(OutFile, TimeStr((J + First DIV 15 ) * 15), ' º')
          ELSE WRITE(OutFile, 'º' : 9);
          FOR I:= 0 TO 4 DO BEGIN
            WRITE(OutFile, DatOut[I, J] : 11);
            IF I = 4 THEN WRITELN(OutFile, 'º')
            ELSE WRITE(OutFile, '³')
          END
        END;
        WRITELN(OutFile, ' È':9,
                'ÍÍÍÍÍÍÍÍÍÍÍÏÍÍÍÍÍÍÍÍÍÍÍÏÍÍÍÍÍÍÍÍÍÍÍÏÍÍÍÍÍÍÍÍÍÍÍÏÍÍÍÍÍÍÍÍÍÍÍ¼');
        WRITELN(OutFile);
        WRITELN(OutFile, 'Classes selected :');
        WRITELN(OutFile, '==================');
        I := 0;
        WHILE Data[I] <> NIL DO BEGIN
          Ptr1 := Data[I];
          WHILE Ptr1 <> NIL DO BEGIN
            IF Ptr1^.InSched THEN
              WRITELN(OutFile, Ptr1^.Name, ' ', Ptr1^.Section, ' ', Ptr1^.Extra);
              Ptr1 := Ptr1^.Next
          END;
          I := I + 1
        END;
        IF FileOut = ''  THEN BEGIN
          REPEAT UNTIL KeyPressed;
          READ(Ch)
        END
      END
    END
  END;  { PrintOut }



{***********************************************************************}
{*                                                                     *}
{*                       Data manipulation                             *}
{*                                                                     *}
{***********************************************************************}
{============================================================================}
{=  Main procedure that will find every possible and imaginable schedule   =}
{=  formed from an original list of classes and sections.                   =}
{============================================================================}
  PROCEDURE MakeSched(Index,Cnt : INTEGER);
    VAR
      Ptr : DataList;
      I : INTEGER;

{============================================================================}
{=  Returns the value true if the section times pointed to by Ptr can fit   =}
{=  in our current schedule.                                                =}
{============================================================================}
    FUNCTION Fit(Ptr : TimeList) : BOOLEAN;
      VAR
        Ptr2 : ClasList;
        Ok : BOOLEAN;
    BEGIN { Fit }
      Ok := TRUE;
      WHILE (Ptr <> NIL) AND Ok DO BEGIN
        Ptr2 := Sched[Ptr^.Day];
        Ok := FALSE;
        WHILE (Ptr2 <> NIL) AND NOT Ok DO BEGIN
          Ok := Ptr2^.T2 >= Ptr^.T2;
          IF NOT Ok THEN Ptr2 := Ptr2^.Next
        END;
        IF Ok THEN Ok := (Ptr2^.T1 <= Ptr^.T1) AND NOT Ptr2^.Used;
        Ptr := Ptr^.Next
      END;
      Fit := Ok
    END;  { Fit }

{============================================================================}
{=
{============================================================================}
    PROCEDURE Coalesce;
      VAR
        Temp,Ptr : ClasList;
        I : INTEGER;
    BEGIN { Coalesce }
      FOR I := 0 TO 4 DO BEGIN
        Ptr := Sched[I];
        WHILE Ptr^.Next <> NIL DO BEGIN
          Temp := Ptr^.Next;
          IF (NOT Ptr^.Used) AND (NOT Temp^.Used) THEN BEGIN
            Ptr^.T2 := Temp^.T2;
            Ptr^.Next := Temp^.Next;
            IF Temp^.Next <> NIL THEN
              Temp^.Next^.Prev := Ptr;
          END ELSE Ptr := Ptr^.Next
        END
      END
    END;  { Coalesce }

{============================================================================}
{============================================================================}
    PROCEDURE Insert(Ptr : DataList);
      VAR
        Temp,Ptr2 : ClasList;
        Ptr1 : TimeList;
    BEGIN { Insert }
      Ptr1 := Ptr^.Times;
      WHILE Ptr1<> NIL DO BEGIN
        Ptr2 := Sched[Ptr1^.Day];
        WHILE Ptr2^.T2 < Ptr1^.T2 DO Ptr2 := Ptr2^.Next;
        { split current free block }
        IF Ptr2^.T1 < Ptr1^.T1 THEN BEGIN
          NEW(Temp);
          Temp^.T1 := Ptr2^.T1;
          Temp^.T2 := Ptr1^.T1 - 1;
          IF Ptr2^.Prev <> NIL THEN Ptr2^.Prev^.Next := Temp
          ELSE Sched[Ptr1^.Day] := Temp;
          Temp^.Prev := Ptr2^.Prev;
          Temp^.Next := Ptr2;
          Ptr2^.Prev := Temp;
          Temp^.Used := FALSE
        END;
        IF Ptr2^.T2 > Ptr1^.T2 THEN BEGIN
          NEW(Temp);
          Temp^.T1 := Ptr1^.T2+1;
          Temp^.T2 := Ptr2^.T2;
          IF Ptr2^.Next <> NIL THEN
            Ptr2^.Next^.Prev := Temp;
          Temp^.Prev := Ptr2;
          Temp^.Next := Ptr2^.Next;
          Ptr2^.Next := Temp;
          Temp^.Used := FALSE
        END;
        Ptr2^.T1 := Ptr1^.T1;
        Ptr2^.T2 := Ptr1^.T2;
        Ptr2^.Info := Ptr;
        Ptr2^.Used := TRUE;
        Ptr1 := Ptr1^.Next;
      END;
      Coalesce;
    END;  { Insert }

{============================================================================}
{============================================================================}
    PROCEDURE Remove(Ptr : TimeList);
      VAR
        Temp,Ptr2 : ClasList;
    BEGIN { Remove }
      WHILE Ptr <> NIL DO BEGIN
        Ptr2 := Sched[Ptr^.Day];
        WHILE Ptr2^.T1 <> Ptr^.T1 DO Ptr2 := Ptr2^.Next;
        Ptr2^.Used := FALSE;
        Ptr := Ptr^.Next
      END;
      Coalesce;
    END;  { Remove }

  BEGIN { MakeSched }
    IF (Data[Index] <> NIL) AND (Cnt < Max) THEN BEGIN
      Ptr := Data[Index];
      WHILE Ptr <> NIL DO BEGIN
        IF Fit(Ptr^.Times) THEN BEGIN
          Ptr^.InSched := TRUE;
          Insert(Ptr);
          MakeSched(Index + 1, Cnt + 1);
          Ptr^.InSched := FALSE;
          Remove(Ptr^.Times)
        END;
        Ptr := Ptr^.Next
      END;
      MakeSched(Index + 1,Cnt)
    END ELSE
    IF Cnt = Max THEN IF FileOut = '' THEN PrintOut(OUTPUT)
             ELSE PrintOut(OutFile)
  END;  { MakeSched }


{============================================================================}
{============================================================================}
  FUNCTION Yes(X, Y : INTEGER; Default : CHAR) : BOOLEAN;
    VAR
      Ch : CHAR;
  BEGIN { Yes }
    REPEAT
      GOTOXY(X,Y); WRITE(Default);
      GOTOXY(X,Y); READ(KBD,Ch);
      GOTOXY(X,Y); WRITE(Ch)
    UNTIL Ch IN ['y','Y','n','N',CHR(13)];
    IF Ch = CHR(13) THEN Ch := Default;
    Yes := (Ch = 'Y') OR (Ch = 'y')
  END;  { Yes }



BEGIN { Schedule }
  ClrScr;
  GOTOXY(0,0);
  WRITELN(Stars);
  WRITELN('*','Schedule Maker 2.2  -  by Mallku Caballero':55,'*':14);
  WRITELN(Stars);
  GOTOXY(5,6); WRITE('Enter schedule file : ');
  REPEAT
    GOTOXY(27,6);
    READLN(FileIn);
    ASSIGN(InFile, FileIn);
    {$I-} RESET(Infile) {$I+};
    Ok := IOResult = 0;
    IF NOT Ok THEN BEGIN
      GOTOXY(45, 6); WRITE('Can not find file -- hit any key',CHR(7));
      READ(KBD, Ch);
      GOTOXY(27, 6); WRITE('                                                           ')
    END
  UNTIL Ok;
  ASSIGN(InFile,FileIn);
  RESET(Infile);
  ReadData;
  GOTOXY(5,7); WRITE('How many classes ?');
  Max := 0;
  REPEAT
    GOTOXY(27,7);
    READLN(MaxStr);
    VAL(MaxStr,Max,Code);
  UNTIL (Code = 0) AND (Max > 0);
  EarlyDef := LTRIM(TimeStr(First));
  LateDef  := LTRIM(TimeStr(Last));
  GOTOXY(5,8); WRITE('Earliest time :       ',EarlyDef);
  GOTOXY(27,8); READLN(Earliest);
  Earliest := Copy(Earliest + Copy(EarlyDef,LENGTH(Earliest)+1,7),1,8);
  GOTOXY(5,9); WRITE('Latest time :         ',LateDef);
  GOTOXY(27,9); READLN(Latest);
  Latest := COPY(Latest + COPY(LateDef,LENGTH(Latest)+1,7),1,8);
  ConvertTime(Earliest,First);
  ConvertTime(Latest,Last);
  FOR I := 0 TO 4 DO BEGIN
    NEW(Sched[I]);
    Sched[I]^.Used := FALSE;
    Sched[I]^.T1 := First;
    Sched[I]^.T2 := Last;
    Sched[I]^.Prev := NIL;
    Sched[I]^.Next := NIL
  END;
  SchedNum := 0;
  OptNum := 0;
  NoOutput := TRUE;
  Normal := TRUE;
  MakeSched(0,0);
  GOTOXY(5,13);
  IF SchedNum > 0 THEN BEGIN
    WRITE(SchedNum:3,' schedules have been generated.');
    GOTOXY(5,14); WRITE(OptNum:3,' optimized schedules generated.');
    GOTOXY(5,16); WRITE('Display   (Y/N) ? ');
    IF Yes(36,16,'Y') THEN BEGIN
      GOTOXY(5,17); WRITE('Optimized (Y/N) ?');
      IF optNum > 0 THEN
         Normal := NOT Yes(36,17,'Y')
      else
         Normal := NOT Yes(36,17,'N');
      GOTOXY(5,18); WRITE('Output file (CR for display) : ');
      READLN(FileOut);
      IF FileOut <> '' THEN BEGIN
        ASSIGN(OutFile, FileOut);
        REWRITE(OutFile)
      END;
      NoOutput := FALSE;
      SchedNum := 0;
      MakeSched(0,0);
      IF FileOut <> '' THEN CLOSE(OutFile)
    END
  END
  ELSE WRITE('Unable to generate any schedules.');
  EraseInfo;
END.  { Schedule }
