Subversion Repositories jumper

Rev

Rev 19 | Rev 22 | 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, 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.   TField = record
  41.     FieldType: TFieldType;
  42.     Goal: Boolean;
  43.     Panel: TPanel;
  44.     Stone: TImage;
  45.   end;
  46.  
  47.   TGoalStatus = (gsUndefined, gsNoGoal, gsMultipleStonesRemaining, gsLastStoneInGoalRed, gsLastStoneInGoalYellow, gsLastStoneInGoalGreen, gsLastStoneOutsideGoal);
  48.  
  49.   TFieldState = (fsUndefined, fsError, fsLocked, fsAvailable, fsStone);
  50.  
  51.   TPlayGroundMatrix = record
  52.     Fields: array of array of TField;
  53.   public
  54.     function MatrixHasGoal: boolean;
  55.     function GoalFieldType: TFieldType;
  56.     function MatrixWorth: integer;
  57.     procedure ClearMatrix(FreeVCL: boolean);
  58.     function CloneMatrix: TPlayGroundMatrix;
  59.     function FieldState(t: TFieldType): TFieldState; overload;
  60.     function FieldState(f: TField): TFieldState; overload;
  61.     function FieldState(x, y: integer): TFieldState; overload;
  62.   end;
  63.  
  64. function FieldTypeWorth(t: TFieldType): integer;
  65.  
  66. implementation
  67.  
  68. function FieldTypeWorth(t: TFieldType): integer;
  69. begin
  70.   if t = ftGreen then result := 10
  71.   else if t = ftYellow then result := 20
  72.   else if t = ftRed then result := 30
  73.   else result := 0;
  74. end;
  75.  
  76. { TPlayGroundMatrix }
  77.  
  78. function TPlayGroundMatrix.MatrixHasGoal: boolean;
  79. var
  80.   i, j: integer;
  81. begin
  82.   result := false;
  83.   for i := Low(Fields) to High(Fields) do
  84.   begin
  85.     for j := Low(Fields[i]) to High(Fields[i]) do
  86.     begin
  87.       result := result or Fields[i][j].Goal;
  88.     end;
  89.   end;
  90. end;
  91.  
  92. function TPlayGroundMatrix.GoalFieldType: TFieldType;
  93. var
  94.   i, j: integer;
  95. begin
  96.   result := ftEmpty; // Damit der Compiler nicht meckert
  97.   for i := Low(Fields) to High(Fields) do
  98.   begin
  99.     for j := Low(Fields[i]) to High(Fields[i]) do
  100.     begin
  101.       if Fields[i][j].Goal then result := Fields[i][j].FieldType
  102.     end;
  103.   end;
  104. end;
  105.  
  106. function TPlayGroundMatrix.MatrixWorth: integer;
  107. var
  108.   i, j: integer;
  109. begin
  110.   result := 0;
  111.   for i := Low(Fields) to High(Fields) do
  112.   begin
  113.     for j := Low(Fields[i]) to High(Fields[i]) do
  114.     begin
  115.       Inc(result, FieldTypeWorth(Fields[i][j].FieldType));
  116.     end;
  117.   end;
  118. end;
  119.  
  120. procedure TPlayGroundMatrix.ClearMatrix(FreeVCL: boolean);
  121. var
  122.   i, j: integer;
  123. begin
  124.   for i := Low(Fields) to High(Fields) do
  125.   begin
  126.     for j := Low(Fields[i]) to High(Fields[i]) do
  127.     begin
  128.       if FreeVCL then
  129.       begin
  130.         if Assigned(Fields[i][j].Stone) then Fields[i][j].Stone.Free;
  131.         if Assigned(Fields[i][j].Panel) then Fields[i][j].Panel.Free;
  132.       end;
  133.     end;
  134.     SetLength(Fields[i], 0);
  135.   end;
  136.   SetLength(Fields, 0);
  137. end;
  138.  
  139. function TPlayGroundMatrix.CloneMatrix: TPlayGroundMatrix;
  140. var
  141.   i, j: integer;
  142. begin
  143.   SetLength(result.Fields, Length(Fields));
  144.   for i := Low(Fields) to High(Fields) do
  145.   begin
  146.     SetLength(result.Fields[i], Length(Fields[i]));
  147.     for j := Low(Fields[i]) to High(Fields[i]) do
  148.     begin
  149.       result.Fields[i][j].FieldType := Fields[i][j].FieldType;
  150.       result.Fields[i][j].Goal      := Fields[i][j].Goal;
  151.       result.Fields[i][j].Panel     := Fields[i][j].Panel;
  152.       result.Fields[i][j].Stone     := Fields[i][j].Stone;
  153.     end;
  154.   end;
  155. end;
  156.  
  157. function TPlayGroundMatrix.FieldState(t: TFieldType): TFieldState;
  158. begin
  159.   result := fsError;
  160.   case t of
  161.     ftFullSpace: result := fsLocked;
  162.     ftEmpty:     result := fsAvailable;
  163.     ftGreen:     result := fsStone;
  164.     ftYellow:    result := fsStone;
  165.     ftRed:       result := fsStone;
  166.   end;
  167. end;
  168.  
  169. function TPlayGroundMatrix.FieldState(f: TField): TFieldState;
  170. begin
  171.   result := FieldState(f.FieldType);
  172. end;
  173.  
  174. function TPlayGroundMatrix.FieldState(x, y: integer): TFieldState;
  175. begin
  176.   result := fsError;
  177.   if (x < Low(Fields)) or (x > High(Fields)) then exit;
  178.   if (y < Low(Fields[x])) or (y > High(Fields[x])) then exit;
  179.  
  180.   result := FieldState(Fields[x][y]);
  181. end;
  182.  
  183. { TLevel }
  184.  
  185. const NUM_HEADERS = 2;
  186.  
  187. constructor TLevel.Create(ABoardFile: string);
  188. begin
  189.   inherited Create;
  190.   FStringList := TStringList.Create;
  191.   Load(ABoardFile);
  192. end;
  193.  
  194. destructor TLevel.Destroy;
  195. begin
  196.   FreeAndNil(FStringList);
  197.  
  198.   inherited;
  199. end;
  200.  
  201. function TLevel.GetGameMode: TGameMode;
  202. begin
  203.   if LowerCase(FStringList.Strings[1]) = 'mode: normal' then
  204.     result := gmNormal
  205.   else if LowerCase(FStringList.Strings[1]) = 'mode: diagonal' then
  206.     result := gmDiagonal
  207.   else
  208.     result := gmUndefined;
  209. end;
  210.  
  211. procedure TLevel.Load(ABoardFile: string);
  212. var
  213.   i: Integer;
  214. begin
  215.   FStringList.Clear;
  216.   FStringList.LoadFromFile(ABoardFile);
  217.  
  218.   // Remove whitespaces and empty lines
  219.   for i := FStringList.Count-1 downto NUM_HEADERS do
  220.   begin
  221.     FStringList.Strings[i] := StringReplace(FStringList.Strings[i], ' ', '', [rfReplaceAll]);
  222.     if FStringList.Strings[i] = '' then FStringList.Delete(i);
  223.   end;
  224. end;
  225.  
  226. function TLevel.LevelStringToLevelArray(ShowErrors: boolean): TLevelArray;
  227. var
  228.   i: integer;
  229.   t: TFieldType;
  230.   err: TLevelError;
  231.   y: Integer;
  232.   x: Integer;
  233.   Line: string;
  234.   lch, uch: char;
  235.   ch: char;
  236. begin
  237.   // Zuerst nach Fehlern suchen
  238.   err := CheckLevelIntegrity(ShowErrors);
  239.   if err <> leNone then exit;
  240.  
  241.   // Nun Matrix aufbauen
  242.   SetLength(result, 0);
  243.   for i := NUM_HEADERS to FStringList.Count-1 do
  244.   begin
  245.     y := i - NUM_HEADERS;
  246.  
  247.     SetLength(result, Length(result)+1); // add line to matrix
  248.  
  249.     Line := FStringList.Strings[i];
  250.     result[y].Indent := DotsAtBeginning(Line) - DotsAtEnd(Line);
  251.     Line := StringReplace(Line, '.', '', [rfReplaceAll]);
  252.     SetLength(result[y].Fields, Length(Line));
  253.  
  254.     for x := 0 to Length(Line)-1 do
  255.     begin
  256.       ch := Line[x+1];
  257.       lch := LowerCase(ch)[1];
  258.       uch := UpperCase(ch)[1];
  259.  
  260.       t := ftUndefined;
  261.       case lch of
  262.         '*': t := ftFullSpace;
  263.         'e': t := ftEmpty;
  264.         'r': t := ftRed;
  265.         'y': t := ftYellow;
  266.         'g': t := ftGreen;
  267.       end;
  268.  
  269.       result[y].Fields[x].Typ := t;
  270.       result[y].Fields[x].Goal := (ch = uch) and (ch <> lch);
  271.     end;
  272.   end;
  273. end;
  274.  
  275. function TLevel.CheckLevelIntegrity(ShowErrors: boolean): TLevelError;
  276. resourcestring
  277.   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 *.';
  278.   LNG_LVL_UNSUPPORTED_VERSION = 'Level format invalid: Version not supported.';
  279.   LNG_LVL_UNSUPPORTED_MODE = 'Level format invalid: Mode not supported.';
  280.   LNG_LVL_EMPTY_BOARD = 'Level invalid: Board is empty.';
  281.   LNG_LVL_INVALID_LENGTH = 'Level invalid: Lines don''t have an equal amount of elements.';
  282. begin
  283.   result := CheckLevelIntegrity;
  284.   if ShowErrors then
  285.   begin
  286.     case result of
  287.       leNone: ;
  288.       leInvalidElement: MessageDlg(LNG_LVL_INVALID_ELEMENT, mtError, [mbOk], 0);
  289.       leUnsupportedVersion: MessageDlg(LNG_LVL_UNSUPPORTED_VERSION, mtError, [mbOk], 0);
  290.       leUnsupportedMode: MessageDlg(LNG_LVL_UNSUPPORTED_MODE, mtError, [mbOk], 0);
  291.       leEmptyBoard: MessageDlg(LNG_LVL_EMPTY_BOARD, mtError, [mbOk], 0);
  292.       leRowInvalidLength: MessageDlg(LNG_LVL_INVALID_LENGTH, mtError, [mbOk], 0);
  293.     end;
  294.   end;
  295. end;
  296.  
  297. function TLevel.CheckLevelIntegrity: TLevelError;
  298. var
  299.   tmp: string;
  300.   i: Integer;
  301.   Line: string;
  302.   firstLine: string;
  303.   thisLine: string;
  304. begin
  305.   result := leNone;
  306.  
  307.   // Check 1: Ist der Header OK?
  308.  
  309.   if LowerCase(FStringList.Strings[0]) <> 'version 2' then
  310.   begin
  311.     result := leUnsupportedVersion;
  312.     exit;
  313.   end;
  314.  
  315.   if ((LowerCase(FStringList.Strings[1]) <> 'mode: normal') and (LowerCase(FStringList.Strings[1]) <> 'mode: diagonal')) then
  316.   begin
  317.     result := leUnsupportedMode;
  318.     exit;
  319.   end;
  320.  
  321.   // Check 2: Ist das Brett leer?
  322.  
  323.   tmp := '';
  324.   for i := NUM_HEADERS to FStringList.Count-1 do tmp := tmp + FStringList.Strings[i];
  325.   if Trim(StringReplace(tmp, '.', '', [rfReplaceAll])) = '' then
  326.   begin
  327.     result := leEmptyBoard;
  328.     exit;
  329.   end;
  330.  
  331.   // Check 3: Geht das Level nicht in einem Quadrat oder Rechteck auf?
  332.  
  333.   firstLine := StringReplace(FStringList.Strings[NUM_HEADERS], '.', '', [rfReplaceAll]);
  334.   for i := NUM_HEADERS to FStringList.Count-1 do
  335.   begin
  336.     thisLine := StringReplace(FStringList.Strings[i], '.', '', [rfReplaceAll]);
  337.     if Length(thisLine) <> Length(firstLine) then
  338.     begin
  339.       result := leRowInvalidLength; // at row y-NUM_HEADERS
  340.       exit;
  341.     end;
  342.   end;
  343.  
  344.   // Check 4: Gibt es ungültige Elemente in den Zeilen?
  345.  
  346.   for i := NUM_HEADERS to FStringList.Count-1 do
  347.   begin
  348.     Line := FStringList.Strings[i];
  349.  
  350.     Line := StringReplace(Line, '.', '', [rfReplaceAll]);
  351.     Line := StringReplace(Line, '*', '', [rfReplaceAll]);
  352.     Line := StringReplace(Line, 'r', '', [rfReplaceAll, rfIgnoreCase]);
  353.     Line := StringReplace(Line, 'y', '', [rfReplaceAll, rfIgnoreCase]);
  354.     Line := StringReplace(Line, 'g', '', [rfReplaceAll, rfIgnoreCase]);
  355.     Line := StringReplace(Line, 'e', '', [rfReplaceAll, rfIgnoreCase]);
  356.  
  357.     if Length(Line) > 0 then
  358.     begin
  359.       result := leInvalidElement; // at row y-NUM_HEADERS
  360.       Exit;
  361.     end;
  362.   end;
  363.  
  364.   // Check 5: Kann im Level gesprungen werden
  365.  
  366.   { Wird hier nicht abgeprüft, da dafür zuerst der PlayGround gebaut sein muss.
  367.     Es ist außerdem eher ein logischer Fehler, kein Fehler in der Levelstruktur! }
  368. end;
  369.  
  370. end.
  371.