Subversion Repositories jumper

Rev

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