Subversion Repositories spacemission

Rev

Rev 42 | Rev 44 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. unit ComLevelReader;
  2.  
  3. interface
  4.  
  5. uses
  6.   Classes;
  7.  
  8. const
  9.   NumEnemyTypes = 7;
  10.  
  11. type
  12.   TEnemyType = (
  13.     etUnknown,
  14.     etEnemyAttacker,
  15.     etEnemyAttacker2,
  16.     etEnemyAttacker3,
  17.     etEnemyMeteor,
  18.     etEnemyUFO,
  19.     etEnemyUFO2,
  20.     etEnemyBoss
  21.   );
  22.  
  23.   TEnemyAdvent = record
  24.     enemyType: TEnemyType;
  25.     x: integer;
  26.     y: integer;
  27.     lifes: integer;
  28.   end;
  29.  
  30.   TLevelData = class(TPersistent)
  31.   strict private
  32.     procedure SortEnemies;
  33.   strict protected
  34.     procedure AssignTo(Dest: TPersistent); override;
  35.   public
  36.     RasterErzwingen: boolean;
  37.     LevelEditorLength: integer;
  38.     LevelName: string;
  39.     LevelAuthor: string;
  40.     EnemyAdventTable: array of TEnemyAdvent;
  41.     function IndexOfEnemy(x,y:integer;enemyType:TEnemyType;lifes:integer): integer;
  42.     procedure AddEnemy(x,y:integer;enemyType:TEnemyType;lifes:integer);
  43.     procedure DeleteEnemy(i: integer); overload;
  44.     procedure DeleteEnemy(x,y:integer;enemyType:TEnemyType;lifes:integer); overload;
  45.     function CountEnemies: integer;
  46.     function HasBoss: boolean;
  47.     procedure Clear;
  48.     procedure LoadFromStrings(sl: TStrings); // version 0.3 - version 1.2 files
  49.     procedure LoadFromFile(filename: string); // version 0.2 - version 1.2 files
  50.     procedure SaveToStrings(sl: TStrings);
  51.     procedure SaveToFile(filename: string);
  52.     destructor Destroy; override;
  53.   end;
  54.  
  55.   TGameMode = (gmUnknown, gmLevels, gmRandom);
  56.  
  57.   TSaveData = class(TPersistent)
  58.   strict protected
  59.     procedure AssignTo(Dest: TPersistent); override;
  60.   public
  61.     Score: integer;
  62.     Life: integer;
  63.     Level: integer;
  64.     GameMode: TGameMode;
  65.     LevelData: TLevelData;
  66.     procedure Clear;
  67.     procedure LoadFromStrings(sl: TStrings);
  68.     procedure LoadFromFile(filename: string);
  69.     procedure SaveToStrings(sl: TStrings);
  70.     procedure SaveToFile(filename: string);
  71.     destructor Destroy; override;
  72.   end;
  73.  
  74. function GetLevelFileName(lev: integer): string;
  75.  
  76. implementation
  77.  
  78. uses
  79.   SysUtils, StrUtils, Global;
  80.  
  81. const
  82.   DefaultLevelLength = 1200;
  83.  
  84. function GetLevelFileName(lev: integer): string;
  85. begin
  86.   result := OwnDirectory+'Levels\Level '+inttostr(lev)+'.lev'; // Version 0.3+ Level Files
  87.   if not FileExists(Result) then
  88.     result := OwnDirectory+'Levels\Lev'+inttostr(lev)+'A1.lev'; // Version 0.2 Level Files
  89. end;
  90.  
  91. { TLevelData }
  92.  
  93. procedure TLevelData.AssignTo(Dest: TPersistent);
  94. var
  95.   DestLevelData: TLevelData;
  96.   i: integer;
  97. begin
  98.   DestLevelData := Dest as TLevelData;
  99.   if Assigned(DestLevelData) then
  100.   begin
  101.     DestLevelData.RasterErzwingen := Self.RasterErzwingen;
  102.     DestLevelData.LevelEditorLength := Self.LevelEditorLength;
  103.     DestLevelData.LevelName := Self.LevelName;
  104.     DestLevelData.LevelAuthor := Self.LevelAuthor;
  105.     SetLength(DestLevelData.EnemyAdventTable, Length(Self.EnemyAdventTable));
  106.     for i := 0 to Length(Self.EnemyAdventTable) do
  107.     begin
  108.       DestLevelData.EnemyAdventTable[i] := Self.EnemyAdventTable[i];
  109.     end;
  110.   end
  111.   else
  112.   begin
  113.     inherited;
  114.   end;
  115. end;
  116.  
  117. procedure TLevelData.Clear;
  118. begin
  119.   SetLength(EnemyAdventTable, 0);
  120.   LevelEditorLength := DefaultLevelLength;
  121.   LevelName := '';
  122.   LevelAuthor := '';
  123. end;
  124.  
  125. function TLevelData.CountEnemies: integer;
  126. begin
  127.   result := Length(EnemyAdventTable);
  128. end;
  129.  
  130. procedure TLevelData.DeleteEnemy(i: integer);
  131. var
  132.   j: integer;
  133. begin
  134.   for j := i+1 to CountEnemies-1 do
  135.   begin
  136.     EnemyAdventTable[j-1] := EnemyAdventTable[j];
  137.   end;
  138.   SetLength(EnemyAdventTable, Length(EnemyAdventTable)-1);
  139. end;
  140.  
  141. procedure TLevelData.DeleteEnemy(x, y: integer; enemyType: TEnemyType;
  142.   lifes: integer);
  143. begin
  144.   DeleteEnemy(IndexOfEnemy(x, y, enemyType, lifes));
  145. end;
  146.  
  147. destructor TLevelData.Destroy;
  148. begin
  149.   Clear;
  150.   inherited;
  151. end;
  152.  
  153. function TLevelData.HasBoss: boolean;
  154. var
  155.   i: integer;
  156. begin
  157.   for i := 0 to Length(EnemyAdventTable) - 1 do
  158.   begin
  159.     if EnemyAdventTable[i].enemyType = etEnemyBoss then
  160.     begin
  161.       result := true;
  162.       exit;
  163.     end;
  164.   end;
  165.   result := false;
  166. end;
  167.  
  168. procedure TLevelData.AddEnemy(x,y:integer;enemyType:TEnemyType;lifes:integer);
  169. begin
  170.   SetLength(EnemyAdventTable, Length(EnemyAdventTable)+1);
  171.  
  172.   if enemyType = etEnemyMeteor then lifes := 0;
  173.   if RasterErzwingen then
  174.   begin
  175.     if x mod RasterW <> 0 then raise Exception.CreateFmt('X-Koordinate muss ohne Rest durch %d teilbar sein', [RasterW]);
  176.     if y mod RasterH <> 0 then raise Exception.CreateFmt('Y-Koordinate muss ohne Rest durch %d teilbar sein', [RasterH]);
  177.   end;
  178.   if lifes > MaxPossibleEnemyLives then lifes := MaxPossibleEnemyLives;
  179.  
  180.   EnemyAdventTable[Length(EnemyAdventTable)-1].x         := x;
  181.   EnemyAdventTable[Length(EnemyAdventTable)-1].y         := y;
  182.   EnemyAdventTable[Length(EnemyAdventTable)-1].enemyType := enemyType;
  183.   EnemyAdventTable[Length(EnemyAdventTable)-1].lifes     := lifes;
  184. end;
  185.  
  186. function TLevelData.IndexOfEnemy(x, y: integer; enemyType: TEnemyType;
  187.   lifes: integer): integer;
  188. var
  189.   i: integer;
  190. begin
  191.   for i := 0 to Length(EnemyAdventTable) - 1 do
  192.   begin
  193.     if (EnemyAdventTable[i].x = x) and
  194.        (EnemyAdventTable[i].y = y) and
  195.        (EnemyAdventTable[i].enemyType = enemyType) and
  196.        (EnemyAdventTable[i].lifes = lifes) then
  197.     begin
  198.       result := i;
  199.       exit;
  200.     end;
  201.   end;
  202.   result := -1;
  203. end;
  204.  
  205. procedure TLevelData.LoadFromStrings(sl: TStrings);
  206. var
  207.   curline: integer;
  208.   z, act: integer;
  209.   sl2: TStringList;
  210.   tmpX, tmpY, tmpLifes: integer;
  211.   tmpEnemy: TEnemyType;
  212.   tmpRest: string;
  213.   ergebnis: string;
  214. begin
  215.   Clear;
  216.  
  217.   RasterErzwingen := true; // wichtig für AddEnemy()
  218.  
  219.   LevelEditorLength := DefaultLevelLength;
  220.   LevelName := '';
  221.   LevelAuthor := '';
  222.  
  223.   if sl.Strings[0] = '; SpaceMission 0.3' then
  224.   begin
  225.     {$REGION 'Backwards compatibility level format 0.3 (convert to 0.4)'}
  226.     sl.Strings[0] := '; SpaceMission 0.4';
  227.     sl.Insert(1, '; LEV-File');
  228.     {$ENDREGION}
  229.   end;
  230.  
  231.   if (sl.Strings[0] = '; SpaceMission 0.4') and
  232.      (sl.Strings[1] = '; LEV-File') then
  233.   begin
  234.     {$REGION 'Backwards compatibility level format 0.4 (convert to 1.0)'}
  235.     sl2 := TStringList.Create;
  236.     try
  237.       z := 0;
  238.       act := 0;
  239.       while z < sl.Count do
  240.       begin
  241.         inc(z);
  242.         if z > 2 then inc(act);
  243.         if act = 5 then act := 1;
  244.         ergebnis := sl.Strings[z-1];
  245.         if ergebnis = '; SpaceMission 0.4' then
  246.           sl2.Add('; SpaceMission 1.0')
  247.         else
  248.         begin
  249.           if (ergebnis = '30000') and (z = 3) then
  250.             sl2.Add(IntTostr(DefaultLevelLength))
  251.           else
  252.           begin
  253.             //if not (((ergebnis = '0') and (z = 4)) or ((ergebnis = '-624') and (z = 5)) or ((ergebnis = '222') and (z = 6)) or ((ergebnis = '3') and (z = 7))) then
  254.             if (z < 4) or (z > 7) then
  255.             begin
  256.               if act = 4 then
  257.                 sl2.Add(inttostr(strtoint(ergebnis) + 32 - (5 * (strtoint(ergebnis) div 37))))
  258.               else
  259.                 sl2.Add(Ergebnis);
  260.             end;
  261.           end;
  262.         end;
  263.       end;
  264.       sl.Text := sl2.Text;
  265.     finally
  266.       FreeAndNil(sl2);
  267.     end;
  268.     {$ENDREGION}
  269.   end;
  270.  
  271.   if (sl.Strings[0] = '; SpaceMission 1.0') and
  272.      (sl.Strings[1] = '; LEV-File') then
  273.   begin
  274.     {$REGION 'Level format 1.0'}
  275.     LevelEditorLength := StrToInt(sl.Strings[2]);
  276.     curline := 3;
  277.     while curline < sl.Count do
  278.     begin
  279.       tmpEnemy := TEnemyType(strtoint(sl.Strings[curline]));
  280.       Inc(curline);
  281.       tmpX := strtoint(sl.Strings[curline]);
  282.       Inc(curline);
  283.       tmpY := strtoint(sl.Strings[curline]);
  284.       Inc(curline);
  285.       tmpLifes := strtoint(sl.Strings[curline]);
  286.       Inc(curline);
  287.       AddEnemy(tmpX, tmpY, tmpEnemy, tmpLifes);
  288.     end;
  289.     {$ENDREGION}
  290.   end
  291.   else if (SameText(sl.Strings[0], '[SpaceMission Level, Format 1.2]')) or
  292.           (SameText(sl.Strings[0], '[SpaceMission Savegame, Format 1.2]')) then
  293.   begin
  294.     RasterErzwingen := SameText(sl.Strings[0], '[SpaceMission Level, Format 1.2]');
  295.     {$REGION 'Level format 1.2'}
  296.     for curline := 1 to sl.Count-1 do
  297.     begin
  298.       // 1234567890123456789012345678901234567890
  299.       // 123456 123456 123456 123456 123456 ; Kommentar
  300.       if (sl.Strings[curline].Trim = '') or
  301.          (Copy(sl.Strings[curline], 1, 1) = ';') then
  302.         // Do nothing
  303.       else if Copy(sl.Strings[curline], 1, 6).TrimRight = 'Width' then
  304.       begin
  305.         LevelEditorLength := StrToInt(TrimRight(Copy(sl.Strings[curline], 8, 6)))
  306.       end
  307.       else if Copy(sl.Strings[curline], 1, 6).TrimRight = 'Name' then
  308.       begin
  309.         LevelName := TrimRight(Copy(sl.Strings[curline], 8, Length(sl.Strings[curline])))
  310.       end
  311.       else if Copy(sl.Strings[curline], 1, 6).TrimRight = 'Author' then
  312.       begin
  313.         LevelAuthor := TrimRight(Copy(sl.Strings[curline], 8, Length(sl.Strings[curline])))
  314.       end
  315.       else if Copy(sl.Strings[curline], 1, 6).TrimRight = 'Enemy' then
  316.       begin
  317.         tmpEnemy := TEnemyType(strtoint(TrimRight(Copy(sl.Strings[curline], 8, 6))));
  318.         tmpX     := strtoint(TrimRight(Copy(sl.Strings[curline], 15, 6)));
  319.         tmpY     := strtoint(TrimRight(Copy(sl.Strings[curline], 22, 6)));
  320.         tmpLifes := strtoint(TrimRight(Copy(sl.Strings[curline], 29, 6)));
  321.         tmpRest  := Copy(sl.Strings[curline], 36, Length(sl.Strings[curline])-36+1);
  322.         if (Copy(tmpRest, 1, 1) <> ';') and (Trim(tmpRest) <> '') then
  323.           raise Exception.CreateFmt('Zeile %d ist ungültig (Zusatzinfo am Ende)', [curline+1]);
  324.         AddEnemy(tmpX, tmpY, tmpEnemy, tmpLifes);
  325.       end;
  326.     end;
  327.     {$ENDREGION}
  328.   end
  329.   else
  330.   begin
  331.     raise Exception.Create('Level-Format nicht unterstützt oder Datei ist beschädigt');
  332.   end;
  333.  
  334.   SortEnemies; // Sortierung nach X-Koordinate ist sehr wichtig für das Spiel!
  335. end;
  336.  
  337. procedure TLevelData.LoadFromFile(filename: string);
  338. var
  339.   sl: TStringList;
  340.   i, j: integer;
  341.   temp: string;
  342.   m: array[1..6] of tstrings;
  343. begin
  344.   sl := TStringList.Create;
  345.   try
  346.     if EndsText('A1.lev', filename) then
  347.     begin
  348.       {$REGION 'Backwards compatibility level format 0.2 (split into 5-6 files; convert to 0.3)'}
  349.       m[1] := TStringList.create;
  350.       m[2] := TStringList.create;
  351.       m[3] := TStringList.create;
  352.       m[4] := TStringList.create;
  353.       m[5] := TStringList.create;
  354.       m[6] := TStringList.create;
  355.       try
  356.         for i := 1 to 6 do
  357.         begin
  358.           filename[Length(filename)-4] := IntToStr(i)[1]; // ...A2.sav, ...A3.sav, etc.
  359.           if FileExists(filename) then
  360.             m[i].loadfromfile(filename);
  361.         end;
  362.         m[1].strings[0] := '-624';
  363.         if m[6].Text = '' then m[6].Text := '30000';
  364.  
  365.         sl.Add('; SpaceMission 0.3');
  366.         sl.Add(temp);
  367.         for j := 0 to m[1].count-2 do
  368.         begin
  369.           for i := 0 to m[1].count-2 do
  370.           begin
  371.             if strtoint(m[1].strings[i]) > strtoint(m[1].strings[i+1]) then
  372.             begin
  373.               m[1].exchange(i, i+1);
  374.               m[2].exchange(i, i+1);
  375.               m[3].exchange(i, i+1);
  376.               m[4].exchange(i, i+1);
  377.               m[5].exchange(i, i+1);
  378.             end;
  379.           end;
  380.         end;
  381.         for i := 0 to m[3].count-1 do
  382.         begin
  383.           for j := 1 to 4 do
  384.           begin
  385.             if j = 1 then sl.Add(m[3].strings[i]);
  386.             if j = 2 then sl.Add(m[1].strings[i]);
  387.             if j = 3 then sl.Add(m[2].strings[i]);
  388.             if j = 4 then sl.Add(m[4].strings[i]);
  389.           end;
  390.         end;
  391.       finally
  392.         FreeAndNil(m[1]);
  393.         FreeAndNil(m[2]);
  394.         FreeAndNil(m[3]);
  395.         FreeAndNil(m[4]);
  396.         FreeAndNil(m[5]);
  397.         FreeAndNil(m[6]);
  398.       end;
  399.       {$ENDREGION}
  400.     end
  401.     else
  402.     begin
  403.       sl.LoadFromFile(filename);
  404.     end;
  405.  
  406.     LoadFromStrings(sl);
  407.   finally
  408.     FreeAndNil(sl);
  409.   end;
  410. end;
  411.  
  412. procedure TLevelData.SaveToStrings(sl: TStrings);
  413. var
  414.   i: integer;
  415. begin
  416.   sl.Clear;
  417.   sl.Add('[SpaceMission Level, Format 1.2]');
  418.   if LevelName   <> '' then sl.Add('Name   ' + LevelName);
  419.   if LevelAuthor <> '' then sl.Add('Author ' + LevelAuthor);
  420.   sl.Add('Width  ' + IntToStr(LevelEditorLength));
  421.   SortEnemies;
  422.   for i := 0 to Length(EnemyAdventTable)-1 do
  423.   begin
  424.     sl.Add(
  425.       'Enemy'.PadRight(6, ' ')+
  426.       ' '+
  427.       IntToStr(Ord(EnemyAdventTable[i].enemyType)).PadRight(6, ' ')+
  428.       ' '+
  429.       IntToStr(EnemyAdventTable[i].x).PadRight(6, ' ')+
  430.       ' '+
  431.       IntToStr(EnemyAdventTable[i].y).PadRight(6, ' ')+
  432.       ' '+
  433.       IntToStr(EnemyAdventTable[i].lifes).PadRight(6, ' ')+
  434.       ' '
  435.     );
  436.   end;
  437. end;
  438.  
  439. procedure TLevelData.SaveToFile(filename: string);
  440. var
  441.   sl: TStringList;
  442. begin
  443.   sl := TStringList.Create;
  444.   try
  445.     SaveToStrings(sl);
  446.     sl.SaveToFile(filename);
  447.   finally
  448.     FreeAndNil(sl);
  449.   end;
  450. end;
  451.  
  452. procedure TLevelData.SortEnemies;
  453. var
  454.   i, n: integer;
  455.   e: TEnemyAdvent;
  456. begin
  457.   // Bubble Sort Algorithmus
  458.   for n := Length(EnemyAdventTable) downto 2 do
  459.   begin
  460.     for i := 0 to n - 2 do
  461.     begin
  462.       if
  463.         // Sort by X-coord (important for the game!)
  464.         (EnemyAdventTable[i].x > EnemyAdventTable[i+1].x)
  465.         or
  466.         // Sort by Y-coord (just cosmetics)
  467.         ((EnemyAdventTable[i].x = EnemyAdventTable[i+1].x) and (EnemyAdventTable[i].y > EnemyAdventTable[i+1].y))
  468.       then
  469.       begin
  470.         e := EnemyAdventTable[i];
  471.         EnemyAdventTable[i] := EnemyAdventTable[i + 1];
  472.         EnemyAdventTable[i + 1] := e;
  473.       end;
  474.     end;
  475.   end;
  476. end;
  477.  
  478. { TSaveData }
  479.  
  480. procedure TSaveData.AssignTo(Dest: TPersistent);
  481. var
  482.   DestSaveData: TSaveData;
  483. begin
  484.   DestSaveData := Dest as TSaveData;
  485.   if Assigned(DestSaveData) then
  486.   begin
  487.     DestSaveData.Score := Self.Score;
  488.     DestSaveData.Life := Self.Life;
  489.     DestSaveData.Level := Self.Level;
  490.     DestSaveData.GameMode := Self.GameMode;
  491.     if not Assigned(DestSaveData.LevelData) then DestSaveData.LevelData := TLevelData.Create;
  492.     DestSaveData.LevelData.Assign(Self.LevelData);
  493.   end
  494.   else
  495.   begin
  496.     inherited;
  497.   end;
  498. end;
  499.  
  500. procedure TSaveData.Clear;
  501. begin
  502.   Score := 0;
  503.   Life := 0;
  504.   Level := 0;
  505.   GameMode := gmUnknown;
  506.   FreeAndNil(LevelData);
  507. end;
  508.  
  509. destructor TSaveData.Destroy;
  510. begin
  511.   Clear;
  512.   inherited;
  513. end;
  514.  
  515. procedure TSaveData.SaveToStrings(sl: TStrings);
  516. var
  517.   sl2: TStringList;
  518. begin
  519.   sl2 := TStringList.Create;
  520.   try
  521.     sl.Add('[SpaceMission Savegame, Format 1.2]');
  522.     sl.Add('Score  ' + IntToStr(Score));
  523.     sl.Add('Lives  ' + IntToStr(Life));
  524.     sl.Add('Level  ' + IntToStr(Level));
  525.     sl.Add('Mode   ' + IntToStr(Ord(GameMode)));
  526.     LevelData.SaveToStrings(sl2);
  527.     sl2.Delete(0); // Signature
  528.     sl.AddStrings(sl2);
  529.   finally
  530.     FreeAndNil(sl2);
  531.   end;
  532. end;
  533.  
  534. procedure TSaveData.LoadFromStrings(sl: TStrings);
  535. var
  536.   curline: Integer;
  537. begin
  538.   if (sl.Strings[0] = '; SpaceMission 1.0') and
  539.      (sl.Strings[1] = '; SAV-File') then
  540.   begin
  541.     Score    := StrToInt(sl.Strings[2]);
  542.     Life     := StrToInt(sl.Strings[3]);
  543.     Level    := StrToInt(sl.Strings[4]);
  544.     GameMode := TGameMode(StrToInt(sl.Strings[5]));
  545.     if Assigned(LevelData) then FreeAndNil(LevelData);
  546.   end
  547.   else if SameText(sl.Strings[0], '[SpaceMission Savegame, Format 1.2]') then
  548.   begin
  549.     Score    := 0;
  550.     Life     := 0;
  551.     Level    := 0;
  552.     GameMode := gmUnknown;
  553.     for curline := 1 to sl.Count-1 do
  554.     begin
  555.       // 1234567890123456789012345678901234567890
  556.       // 123456 123456 123456 123456 123456 ; Kommentar
  557.       if (sl.Strings[curline].Trim = '') or
  558.          (Copy(sl.Strings[curline], 1, 1) = ';') then
  559.         // Do nothing
  560.       else if Copy(sl.Strings[curline], 1, 6).TrimRight = 'Score' then
  561.       begin
  562.         Score := StrToInt(TrimRight(Copy(sl.Strings[curline], 8, 6)))
  563.       end
  564.       else if Copy(sl.Strings[curline], 1, 6).TrimRight = 'Lives' then
  565.       begin
  566.         Life := StrToInt(TrimRight(Copy(sl.Strings[curline], 8, 6)))
  567.       end
  568.       else if Copy(sl.Strings[curline], 1, 6).TrimRight = 'Level' then
  569.       begin
  570.         Level := StrToInt(TrimRight(Copy(sl.Strings[curline], 8, 6)))
  571.       end
  572.       else if Copy(sl.Strings[curline], 1, 6).TrimRight = 'Mode' then
  573.       begin
  574.         GameMode := TGameMode(StrToInt(TrimRight(Copy(sl.Strings[curline], 8, 6))))
  575.       end;
  576.     end;
  577.     if Assigned(LevelData) then FreeAndNil(LevelData);
  578.     LevelData := TLevelData.Create;
  579.     LevelData.LoadFromStrings(sl);
  580.   end
  581.   else
  582.   begin
  583.     raise Exception.Create('Spielstand-Format nicht unterstützt oder Datei beschädigt');
  584.   end;
  585. end;
  586.  
  587. procedure TSaveData.LoadFromFile(filename: string);
  588. var
  589.   sl: TStringList;
  590. begin
  591.   sl := TStringList.Create;
  592.   try
  593.     sl.LoadFromFile(filename);
  594.     LoadFromStrings(sl);
  595.   finally
  596.     FreeAndNil(sl);
  597.   end;
  598. end;
  599.  
  600. procedure TSaveData.SaveToFile(filename: string);
  601. var
  602.   sl: TStringList;
  603. begin
  604.   sl := TStringList.Create;
  605.   try
  606.     SaveToStrings(sl);
  607.     sl.SaveToFile(filename);
  608.   finally
  609.     FreeAndNil(sl);
  610.   end;
  611. end;
  612.  
  613. end.
  614.