Subversion Repositories jumper

Rev

Rev 11 | Rev 21 | 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 = (ftUndefined, ftFullSpace, ftEmpty, ftRed, ftYellow, ftGreen);
  10.  
  11.   TFieldProperties = record
  12.     Typ: TFieldType;
  13.     Goal: Boolean;
  14.   end;
  15.  
  16.   TGameMode = (gmUndefined, gmNormal, gmDiagonal);
  17.  
  18.   TRow = record
  19.     Indent: integer;
  20.     Fields: array of TFieldProperties;
  21.   end;
  22.   TLevelArray = array of TRow;
  23.  
  24.   TLevelError = (leUndefined, leNone, leInvalidElement, leEmptyBoard, leRowInvalidLength,
  25.                  leUnsupportedVersion, leUnsupportedMode);
  26.  
  27.   TLevel = class(TObject)
  28.   private
  29.     FStringList: TStringList;
  30.     procedure Load(ABoardFile: string);
  31.   public
  32.     constructor Create(ABoardFile: string);
  33.     destructor Destroy; override;
  34.     function LevelStringToLevelArray(ShowErrors: boolean): TLevelArray;
  35.     function CheckLevelIntegrity: TLevelError; overload;
  36.     function CheckLevelIntegrity(ShowErrors: boolean): TLevelError; overload;
  37.     function GetGameMode: TGameMode;
  38.   end;
  39.  
  40. procedure DrawLevelPreview(Level: TLevel; Image: TImage; BackgroundColor: TColor);
  41. function FieldTypeWorth(t: TFieldType): integer;
  42.  
  43. implementation
  44.  
  45. procedure DrawLevelPreview(Level: TLevel; Image: TImage; BackgroundColor: TColor);
  46. var
  47.   LevelArray: TLevelArray;
  48.   y, x: integer;
  49.   t: TFieldType;
  50.   indent: Integer;
  51. const
  52.   PREVIEW_BLOCK_SIZE = 10; // Enthält Field und Abstand
  53.   PREVIEW_TAB_SIZE = PREVIEW_BLOCK_SIZE div 2; // 5
  54. begin
  55.   LevelArray := nil;
  56.  
  57.   ClearImage(Image, BackgroundColor);
  58.  
  59.   LevelArray := Level.LevelStringToLevelArray(false);
  60.  
  61.   for y := Low(LevelArray) to High(LevelArray) do
  62.   begin
  63.     for x := Low(LevelArray[y].Fields) to High(LevelArray[y].Fields) do
  64.     begin
  65.       t      := LevelArray[y].Fields[x].Typ;
  66.       indent := LevelArray[y].Indent;
  67.  
  68.       case t of
  69.         ftFullSpace: Image.Canvas.Brush.Color := BackgroundColor;
  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].Fields[x].Goal then
  77.         Image.Canvas.Pen.Color := clBlack
  78.       else
  79.         Image.Canvas.Pen.Color := BackgroundColor;
  80.  
  81.       Image.Canvas.Rectangle(x*PREVIEW_BLOCK_SIZE + indent*PREVIEW_TAB_SIZE,
  82.                              y*PREVIEW_BLOCK_SIZE,
  83.                              x*PREVIEW_BLOCK_SIZE + indent*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 DotsAtBeginning(s: string): integer;
  141. var
  142.   i: integer;
  143. begin
  144.   result := 0;
  145.   for i := 1 to Length(s) do
  146.   begin
  147.     if s[i] = '.' then
  148.       Inc(result)
  149.     else
  150.       Exit;
  151.   end;
  152. end;
  153.  
  154. function DotsAtEnd(s: string): integer;
  155. var
  156.   i: integer;
  157. begin
  158.   result := 0;
  159.   for i := Length(s) downto 1 do
  160.   begin
  161.     if s[i] = '.' then
  162.       Inc(result)
  163.     else
  164.       Exit;
  165.   end;
  166. end;
  167.  
  168. function TLevel.LevelStringToLevelArray(ShowErrors: boolean): TLevelArray;
  169. var
  170.   i: integer;
  171.   t: TFieldType;
  172.   err: TLevelError;
  173.   y: Integer;
  174.   x: Integer;
  175.   Line: string;
  176.   lch, uch: char;
  177.   ch: char;
  178. begin
  179.   // Zuerst nach Fehlern suchen
  180.   err := CheckLevelIntegrity(ShowErrors);
  181.   if err <> leNone then exit;
  182.  
  183.   // Nun Matrix aufbauen
  184.   SetLength(result, 0);
  185.   for i := NUM_HEADERS to FStringList.Count-1 do
  186.   begin
  187.     y := i - NUM_HEADERS;
  188.  
  189.     SetLength(result, Length(result)+1); // add line to matrix
  190.  
  191.     Line := FStringList.Strings[i];
  192.     result[y].Indent := DotsAtBeginning(Line) - DotsAtEnd(Line);
  193.     Line := StringReplace(Line, '.', '', [rfReplaceAll]);
  194.     SetLength(result[y].Fields, Length(Line));
  195.  
  196.     for x := 0 to Length(Line)-1 do
  197.     begin
  198.       ch := Line[x+1];
  199.       lch := LowerCase(ch)[1];
  200.       uch := UpperCase(ch)[1];
  201.  
  202.       t := ftUndefined;
  203.       case lch of
  204.         '*': t := ftFullSpace;
  205.         'e': t := ftEmpty;
  206.         'r': t := ftRed;
  207.         'y': t := ftYellow;
  208.         'g': t := ftGreen;
  209.       end;
  210.  
  211.       result[y].Fields[x].Typ := t;
  212.       result[y].Fields[x].Goal := (ch = uch) and (ch <> lch);
  213.     end;
  214.   end;
  215. end;
  216.  
  217. function TLevel.CheckLevelIntegrity(ShowErrors: boolean): TLevelError;
  218. resourcestring
  219.   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 *.';
  220.   LNG_LVL_UNSUPPORTED_VERSION = 'Level format invalid: Version not supported.';
  221.   LNG_LVL_UNSUPPORTED_MODE = 'Level format invalid: Mode not supported.';
  222.   LNG_LVL_EMPTY_BOARD = 'Level invalid: Board is empty.';
  223.   LNG_LVL_INVALID_LENGTH = 'Level invalid: Lines don''t have an equal amount of elements.';
  224. begin
  225.   result := CheckLevelIntegrity;
  226.   if ShowErrors then
  227.   begin
  228.     case result of
  229.       leNone: ;
  230.       leInvalidElement: MessageDlg(LNG_LVL_INVALID_ELEMENT, mtError, [mbOk], 0);
  231.       leUnsupportedVersion: MessageDlg(LNG_LVL_UNSUPPORTED_VERSION, mtError, [mbOk], 0);
  232.       leUnsupportedMode: MessageDlg(LNG_LVL_UNSUPPORTED_MODE, mtError, [mbOk], 0);
  233.       leEmptyBoard: MessageDlg(LNG_LVL_EMPTY_BOARD, mtError, [mbOk], 0);
  234.       leRowInvalidLength: MessageDlg(LNG_LVL_INVALID_LENGTH, mtError, [mbOk], 0);
  235.     end;
  236.   end;
  237. end;
  238.  
  239. function TLevel.CheckLevelIntegrity: TLevelError;
  240. var
  241.   tmp: string;
  242.   i: Integer;
  243.   Line: string;
  244.   firstLine: string;
  245.   thisLine: string;
  246. begin
  247.   result := leNone;
  248.  
  249.   // Check 1: Ist der Header OK?
  250.  
  251.   if LowerCase(FStringList.Strings[0]) <> 'version 2' then
  252.   begin
  253.     result := leUnsupportedVersion;
  254.     exit;
  255.   end;
  256.  
  257.   if ((LowerCase(FStringList.Strings[1]) <> 'mode: normal') and (LowerCase(FStringList.Strings[1]) <> 'mode: diagonal')) then
  258.   begin
  259.     result := leUnsupportedMode;
  260.     exit;
  261.   end;
  262.  
  263.   // Check 2: Ist das Brett leer?
  264.  
  265.   tmp := '';
  266.   for i := NUM_HEADERS to FStringList.Count-1 do tmp := tmp + FStringList.Strings[i];
  267.   if Trim(StringReplace(tmp, '.', '', [rfReplaceAll])) = '' then
  268.   begin
  269.     result := leEmptyBoard;
  270.     exit;
  271.   end;
  272.  
  273.   // Check 3: Geht das Level nicht in einem Quadrat oder Rechteck auf?
  274.  
  275.   firstLine := StringReplace(FStringList.Strings[NUM_HEADERS], '.', '', [rfReplaceAll]);
  276.   for i := NUM_HEADERS to FStringList.Count-1 do
  277.   begin
  278.     thisLine := StringReplace(FStringList.Strings[i], '.', '', [rfReplaceAll]);
  279.     if Length(thisLine) <> Length(firstLine) then
  280.     begin
  281.       result := leRowInvalidLength; // at row y-NUM_HEADERS
  282.       exit;
  283.     end;
  284.   end;
  285.  
  286.   // Check 4: Gibt es ungültige Elemente in den Zeilen?
  287.  
  288.   for i := NUM_HEADERS to FStringList.Count-1 do
  289.   begin
  290.     Line := FStringList.Strings[i];
  291.  
  292.     Line := StringReplace(Line, '.', '', [rfReplaceAll]);
  293.     Line := StringReplace(Line, '*', '', [rfReplaceAll]);
  294.     Line := StringReplace(Line, 'r', '', [rfReplaceAll, rfIgnoreCase]);
  295.     Line := StringReplace(Line, 'y', '', [rfReplaceAll, rfIgnoreCase]);
  296.     Line := StringReplace(Line, 'g', '', [rfReplaceAll, rfIgnoreCase]);
  297.     Line := StringReplace(Line, 'e', '', [rfReplaceAll, rfIgnoreCase]);
  298.  
  299.     if Length(Line) > 0 then
  300.     begin
  301.       result := leInvalidElement; // at row y-NUM_HEADERS
  302.       Exit;
  303.     end;
  304.   end;
  305.  
  306.   // Check 5: Kann im Level gesprungen werden
  307.  
  308.   { Wird hier nicht abgeprüft, da dafür zuerst der PlayGround gebaut sein muss.
  309.     Es ist außerdem eher ein logischer Fehler, kein Fehler in der Levelstruktur! }
  310. end;
  311.  
  312. end.
  313.