Subversion Repositories jumper

Rev

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

  1. unit LevelFunctions;
  2.  
  3. interface
  4.  
  5. uses
  6.   SysUtils, Dialogs, Functions, ExtCtrls, Graphics, Classes, Math;
  7.  
  8. type
  9.   TFieldType = (ftFullSpace, ftHalfSpace, ftEmpty, ftRed, ftYellow, ftGreen);
  10.  
  11.   TFieldProperties = record
  12.     Typ: TFieldType;
  13.     Goal: Boolean;
  14.   end;
  15.  
  16.   TGameMode = (gmUndefined, gmNormal, gmDiagonal);
  17.  
  18.   TLevelArray = array of array of TFieldProperties;
  19.  
  20.   TLevelError = (leNone, leInvalidElement, leEmptyBoard, leRowInvalidLength,
  21.                  leUnsupportedVersion, leUnsupportedMode);
  22.  
  23.   TLevel = class(TObject)
  24.   private
  25.     FStringList: TStringList;
  26.     procedure Load(ABoardFile: string);
  27.   public
  28.     constructor Create(ABoardFile: string);
  29.     destructor Destroy; override;
  30.     function LevelStringToLevelArray(ShowErrors: boolean): TLevelArray;
  31.     function CheckLevelIntegrity: TLevelError; overload;
  32.     function CheckLevelIntegrity(ShowErrors: boolean): TLevelError; overload;
  33.     function GetGameMode: TGameMode;
  34.   end;
  35.  
  36. procedure DrawLevelPreview(Level: TLevel; Image: TImage; BackgroundColor: TColor);
  37. function FieldTypeWorth(t: TFieldType): integer;
  38.  
  39. implementation
  40.  
  41. procedure DrawLevelPreview(Level: TLevel; Image: TImage; BackgroundColor: TColor);
  42. var
  43.   LevelArray: TLevelArray;
  44.   y, x: integer;
  45.   t: TFieldType;
  46.   halftabs: integer;
  47. const
  48.   PREVIEW_BLOCK_SIZE = 10; // Enthält Field und Abstand
  49.   PREVIEW_TAB_SIZE = PREVIEW_BLOCK_SIZE div 2; // 5
  50. begin
  51.   LevelArray := nil;
  52.  
  53.   ClearImage(Image, BackgroundColor);
  54.  
  55.   LevelArray := Level.LevelStringToLevelArray(false);
  56.  
  57.   for y := Low(LevelArray) to High(LevelArray) do
  58.   begin
  59.     halftabs := 0;
  60.     for x := Low(LevelArray[y]) to High(LevelArray[y]) do
  61.     begin
  62.       t := LevelArray[y][x].Typ;
  63.  
  64.       case t of
  65.         ftFullSpace: Image.Canvas.Brush.Color := BackgroundColor;
  66.         ftHalfSpace: begin
  67.           Image.Canvas.Brush.Color := BackgroundColor;
  68.           inc(halftabs);
  69.         end;
  70.         ftEmpty: Image.Canvas.Brush.Color := clWhite;
  71.         ftGreen: Image.Canvas.Brush.Color := clLime;
  72.         ftYellow: Image.Canvas.Brush.Color := clYellow;
  73.         ftRed: Image.Canvas.Brush.Color := clRed;
  74.       end;
  75.  
  76.       if LevelArray[y][x].Goal then
  77.         Image.Canvas.Pen.Color := clBlack
  78.       else
  79.         Image.Canvas.Pen.Color := BackgroundColor;
  80.  
  81.       Image.Canvas.Rectangle((x-halftabs)*PREVIEW_BLOCK_SIZE + halftabs*PREVIEW_TAB_SIZE,
  82.                              y*PREVIEW_BLOCK_SIZE,
  83.                              (x-halftabs)*PREVIEW_BLOCK_SIZE + halftabs*PREVIEW_TAB_SIZE + PREVIEW_BLOCK_SIZE,
  84.                              y*PREVIEW_BLOCK_SIZE                                        + PREVIEW_BLOCK_SIZE);
  85.     end;
  86.   end;
  87. end;
  88.  
  89. function FieldTypeWorth(t: TFieldType): integer;
  90. begin
  91.   if t = ftGreen then result := 10
  92.   else if t = ftYellow then result := 20
  93.   else if t = ftRed then result := 30
  94.   else result := 0;
  95. end;
  96.  
  97. { TLevel }
  98.  
  99. const NUM_HEADERS = 2;
  100.  
  101. constructor TLevel.Create(ABoardFile: string);
  102. begin
  103.   inherited Create;
  104.   FStringList := TStringList.Create;
  105.   Load(ABoardFile);
  106. end;
  107.  
  108. destructor TLevel.Destroy;
  109. begin
  110.   FreeAndNil(FStringList);
  111.  
  112.   inherited;
  113. end;
  114.  
  115. function TLevel.GetGameMode: TGameMode;
  116. begin
  117.   if LowerCase(FStringList.Strings[1]) = 'mode: normal' then
  118.     result := gmNormal
  119.   else if LowerCase(FStringList.Strings[1]) = 'mode: diagonal' then
  120.     result := gmDiagonal
  121.   else
  122.     result := gmUndefined;
  123. end;
  124.  
  125. procedure TLevel.Load(ABoardFile: string);
  126. var
  127.   i: Integer;
  128. begin
  129.   FStringList.Clear;
  130.   FStringList.LoadFromFile(ABoardFile);
  131.  
  132.   // Remove whitespaces and empty lines
  133.   for i := FStringList.Count-1 downto NUM_HEADERS do
  134.   begin
  135.     FStringList.Strings[i] := StringReplace(FStringList.Strings[i], ' ', '', [rfReplaceAll]);
  136.     if FStringList.Strings[i] = '' then FStringList.Delete(i);
  137.   end;
  138. end;
  139.  
  140. function TLevel.LevelStringToLevelArray(ShowErrors: boolean): TLevelArray;
  141. var
  142.   i: integer;
  143.   t: TFieldType;
  144.   err: TLevelError;
  145.   longestLine: Integer;
  146.   thisLine: Integer;
  147.   y: Integer;
  148.   x: Integer;
  149.   Line: string;
  150.   lch, uch: char;
  151.   ch: char;
  152. begin
  153.   // Zuerst nach Fehlern suchen
  154.   err := CheckLevelIntegrity(ShowErrors);
  155.   if err <> leNone then exit;
  156.  
  157.   // Längste Zeile finden
  158.   longestLine := 0;
  159.   for i := NUM_HEADERS to FStringList.Count-1 do
  160.   begin
  161.     longestLine := Max(longestLine, Length(FStringList.Strings[i]));
  162.   end;
  163.  
  164.   // Nun Matrix aufbauen
  165.   SetLength(result, 0);
  166.   for i := NUM_HEADERS to FStringList.Count-1 do
  167.   begin
  168.     y := i - NUM_HEADERS;
  169.  
  170.     SetLength(result, Length(result)+1); // add line to matrix
  171.     SetLength(result[y], longestLine);
  172.  
  173.     Line := FStringList.Strings[i];
  174.  
  175.     for x := 0 to LongestLine-1 do
  176.     begin
  177.       ch := Copy(Line,x+1,1)[1];
  178.       lch := LowerCase(ch)[1];
  179.       uch := UpperCase(ch)[1];
  180.       case lch of
  181.         '*': t := ftFullSpace;
  182.         '.': t := ftHalfSpace;
  183.         'e': t := ftEmpty;
  184.         'r': t := ftRed;
  185.         'y': t := ftYellow;
  186.         'g': t := ftGreen;
  187.       end;
  188.  
  189.       result[y][x].Typ := t;
  190.       result[y][x].Goal := (ch = uch) and (ch <> lch);
  191.     end;
  192.   end;
  193. end;
  194.  
  195. function TLevel.CheckLevelIntegrity(ShowErrors: boolean): TLevelError;
  196. resourcestring
  197.   LNG_LVL_INVALID_ELEMENT = 'Level invalid: There are invalid elements in the file.'+#13#10#13#10+'Valid elements are r/R, y/Y, g/G, e/E, . and *.';
  198.   LNG_LVL_UNSUPPORTED_VERSION = 'Level format invalid: Version not supported.';
  199.   LNG_LVL_UNSUPPORTED_MODE = 'Level format invalid: Mode not supported.';
  200.   LNG_LVL_EMPTY_BOARD = 'Level invalid: Board is empty.';
  201.   LNG_LVL_INVALID_LENGTH = 'Level invalid: Lines don''t have an equal amount of elements.';
  202. begin
  203.   result := CheckLevelIntegrity;
  204.   if ShowErrors then
  205.   begin
  206.     case result of
  207.       leNone: ;
  208.       leInvalidElement: MessageDlg(LNG_LVL_INVALID_ELEMENT, mtError, [mbOk], 0);
  209.       leUnsupportedVersion: MessageDlg(LNG_LVL_UNSUPPORTED_VERSION, mtError, [mbOk], 0);
  210.       leUnsupportedMode: MessageDlg(LNG_LVL_UNSUPPORTED_MODE, mtError, [mbOk], 0);
  211.       leEmptyBoard: MessageDlg(LNG_LVL_EMPTY_BOARD, mtError, [mbOk], 0);
  212.       leRowInvalidLength: MessageDlg(LNG_LVL_INVALID_LENGTH, mtError, [mbOk], 0);
  213.     end;
  214.   end;
  215. end;
  216.  
  217. function TLevel.CheckLevelIntegrity: TLevelError;
  218. var
  219.   W: integer;
  220.   H: extended;
  221.   header, h_ver, h_dia, h_del, tmp: string;
  222.   p: integer;
  223.   i: Integer;
  224.   Line: string;
  225. begin
  226.   result := leNone;
  227.  
  228.   // Check 1: Ist der Header OK?
  229.  
  230.   if LowerCase(FStringList.Strings[0]) <> 'version 2' then
  231.   begin
  232.     result := leUnsupportedVersion;
  233.     exit;
  234.   end;
  235.  
  236.   if ((LowerCase(FStringList.Strings[1]) <> 'mode: normal') and (LowerCase(FStringList.Strings[1]) <> 'mode: diagonal')) then
  237.   begin
  238.     result := leUnsupportedMode;
  239.     exit;
  240.   end;
  241.  
  242.   // Check 2: Ist das Brett leer?
  243.  
  244.   if FStringList.Count - NUM_HEADERS = 0 then
  245.   begin
  246.     result := leEmptyBoard;
  247.     exit;
  248.   end;
  249.  
  250.   // Check 3: Geht das Level nicht in einem Quadrat oder Rechteck auf?
  251.  
  252.   for i := NUM_HEADERS to FStringList.Count-1 do
  253.   begin
  254.     if Length(FStringList.Strings[i]) <> Length(FStringList.Strings[NUM_HEADERS]) then
  255.     begin
  256.       result := leRowInvalidLength; // at row y-NUM_HEADERS
  257.       exit;
  258.     end;
  259.   end;
  260.  
  261.   // Check 4: Gibt es ungültige Elemente in den Zeilen?
  262.  
  263.   for i := NUM_HEADERS to FStringList.Count-1 do
  264.   begin
  265.     Line := FStringList.Strings[i];
  266.  
  267.     Line := StringReplace(Line, '.', '', [rfReplaceAll]);
  268.     Line := StringReplace(Line, '*', '', [rfReplaceAll]);
  269.     Line := StringReplace(Line, 'r', '', [rfReplaceAll, rfIgnoreCase]);
  270.     Line := StringReplace(Line, 'y', '', [rfReplaceAll, rfIgnoreCase]);
  271.     Line := StringReplace(Line, 'g', '', [rfReplaceAll, rfIgnoreCase]);
  272.     Line := StringReplace(Line, 'e', '', [rfReplaceAll, rfIgnoreCase]);
  273.  
  274.     if Length(Line) > 0 then
  275.     begin
  276.       result := leInvalidElement; // at row y-NUM_HEADERS
  277.       Exit;
  278.     end;
  279.   end;
  280.  
  281.   // Check 5: Kann im Level gesprungen werden
  282.  
  283.   { Wird hier nicht abgeprüft, da dafür zuerst der PlayGround gebaut sein muss.
  284.     Es ist außerdem eher ein logischer Fehler, kein Fehler in der Levelstruktur! }
  285. end;
  286.  
  287. end.
  288.