Subversion Repositories jumper

Rev

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