Subversion Repositories jumper

Rev

Rev 22 | Rev 24 | 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.   TFieldProperties = record
  17.     Typ: TFieldType;
  18.     Goal: Boolean;
  19.   end;
  20.  
  21.   TGameMode = (gmUndefined, gmNormal, gmDiagonal);
  22.  
  23.   TRow = record
  24.     Indent: integer;
  25.     Fields: array of TFieldProperties;
  26.   end;
  27.   TLevelArray = array of TRow;
  28.  
  29.   TLevelError = (leUndefined, leNone, leInvalidElement, leEmptyBoard, leRowInvalidLength,
  30.                  leUnsupportedVersion, leUnsupportedMode);
  31.  
  32.   TLevel = class(TObject)
  33.   private
  34.     FStringList: TStringList;
  35.     procedure Load(ABoardFile: string);
  36.     function GetGameMode: TGameMode;
  37.   public
  38.     constructor Create(ABoardFile: string);
  39.     destructor Destroy; override;
  40.     function LevelStringToLevelArray(ShowErrors: boolean): TLevelArray;
  41.     function CheckLevelIntegrity: TLevelError; overload;
  42.     function CheckLevelIntegrity(ShowErrors: boolean): TLevelError; overload;
  43.     property GameMode: TGameMode read GetGameMode;
  44.   end;
  45.  
  46.   TField = record
  47.     FieldType: TFieldType;
  48.     Goal: Boolean;
  49.     Panel: TPanel;
  50.     Stone: TImage;
  51.   end;
  52.  
  53.   TGoalStatus = (gsUndefined, gsNoGoal, gsMultipleStonesRemaining, gsLastStoneInGoalRed, gsLastStoneInGoalYellow, gsLastStoneInGoalGreen, gsLastStoneOutsideGoal);
  54.  
  55.   TFieldState = (fsUndefined, fsError, fsLocked, fsAvailable, fsStone);
  56.  
  57.   TPlayGroundMatrix = record
  58.     Fields: array of array of TField;
  59.   public
  60.     procedure InitFieldArray(width, height: integer);
  61.     function MatrixHasGoal: boolean;
  62.     function GoalFieldType: TFieldType;
  63.     function MatrixWorth: integer;
  64.     procedure ClearMatrix(FreeVCL: boolean);
  65.     function CloneMatrix: TPlayGroundMatrix;
  66.     class function FieldState(t: TFieldType): TFieldState; overload; static;
  67.     function FieldState(f: TField): TFieldState; overload;
  68.     function FieldState(x, y: integer): TFieldState; overload;
  69.     function CanJump(SourceX, SourceY, DestX, DestY: integer; DiagonalOK: boolean): boolean; overload;
  70.     function CanJump(SourceX, SourceY: integer; DiagonalOK: boolean): boolean; overload;
  71.     function CanJump(DiagonalOK: boolean): boolean; overload;
  72.     function IndexToCoord(index: integer): TCoord;
  73.     function CoordToIndex(coord: TCoord): integer; overload;
  74.     function CoordToIndex(x, y: integer): integer; overload;
  75.     function Width: integer;
  76.     function Height: integer;
  77.   end;
  78.  
  79. function FieldTypeWorth(t: TFieldType): integer;
  80.  
  81. implementation
  82.  
  83. function FieldTypeWorth(t: TFieldType): integer;
  84. begin
  85.   if t = ftGreen then result := 10
  86.   else if t = ftYellow then result := 20
  87.   else if t = ftRed then result := 30
  88.   else result := 0;
  89. end;
  90.  
  91. { TPlayGroundMatrix }
  92.  
  93. function TPlayGroundMatrix.MatrixHasGoal: boolean;
  94. var
  95.   x, y: integer;
  96. begin
  97.   result := false;
  98.   for x := Low(Fields) to High(Fields) do
  99.   begin
  100.     for y := Low(Fields[x]) to High(Fields[x]) do
  101.     begin
  102.       result := result or Fields[x,y].Goal;
  103.     end;
  104.   end;
  105. end;
  106.  
  107. function TPlayGroundMatrix.GoalFieldType: TFieldType;
  108. var
  109.   x, y: integer;
  110. begin
  111.   result := ftEmpty; // Damit der Compiler nicht meckert
  112.   for x := Low(Fields) to High(Fields) do
  113.   begin
  114.     for y := Low(Fields[x]) to High(Fields[x]) do
  115.     begin
  116.       if Fields[x,y].Goal then result := Fields[x,y].FieldType
  117.     end;
  118.   end;
  119. end;
  120.  
  121. function TPlayGroundMatrix.Height: integer;
  122. begin
  123.   if Length(Fields) = 0 then
  124.     result := 0
  125.   else
  126.     result := Length(Fields[0]);
  127. end;
  128.  
  129. function TPlayGroundMatrix.IndexToCoord(index: integer): TCoord;
  130. begin
  131.   result.X := index mod Width;
  132.   result.Y := index div Width;
  133. end;
  134.  
  135. procedure TPlayGroundMatrix.InitFieldArray(width, height: integer);
  136. var
  137.   x, y: integer;
  138. begin
  139.   SetLength(Fields, width, height);
  140.   for x := Low(Fields) to High(Fields) do
  141.   begin
  142.     for y := Low(Fields[x]) to High(Fields[x]) do
  143.     begin
  144.       Fields[x,y].FieldType := ftUndefined;
  145.     end;
  146.   end;
  147. end;
  148.  
  149. function TPlayGroundMatrix.MatrixWorth: integer;
  150. var
  151.   x, y: integer;
  152. begin
  153.   result := 0;
  154.   for x := Low(Fields) to High(Fields) do
  155.   begin
  156.     for y := Low(Fields[x]) to High(Fields[x]) do
  157.     begin
  158.       Inc(result, FieldTypeWorth(Fields[x,y].FieldType));
  159.     end;
  160.   end;
  161. end;
  162.  
  163. function TPlayGroundMatrix.Width: integer;
  164. begin
  165.   result := Length(Fields);
  166. end;
  167.  
  168. procedure TPlayGroundMatrix.ClearMatrix(FreeVCL: boolean);
  169. var
  170.   x, y: integer;
  171. begin
  172.   for x := Low(Fields) to High(Fields) do
  173.   begin
  174.     for y := Low(Fields[x]) to High(Fields[x]) do
  175.     begin
  176.       if FreeVCL then
  177.       begin
  178.         if Assigned(Fields[x,y].Stone) then Fields[x,y].Stone.Free;
  179.         if Assigned(Fields[x,y].Panel) then Fields[x,y].Panel.Free;
  180.       end;
  181.     end;
  182.   end;
  183.   SetLength(Fields, 0, 0);
  184. end;
  185.  
  186. function TPlayGroundMatrix.CloneMatrix: TPlayGroundMatrix;
  187. var
  188.   x, y: integer;
  189. begin
  190.   SetLength(result.Fields, Length(Fields));
  191.   for x := Low(Fields) to High(Fields) do
  192.   begin
  193.     SetLength(result.Fields[x], Length(Fields[x]));
  194.     for y := Low(Fields[x]) to High(Fields[x]) do
  195.     begin
  196.       result.Fields[x,y].FieldType := Fields[x,y].FieldType;
  197.       result.Fields[x,y].Goal      := Fields[x,y].Goal;
  198.       result.Fields[x,y].Panel     := Fields[x,y].Panel;
  199.       result.Fields[x,y].Stone     := Fields[x,y].Stone;
  200.     end;
  201.   end;
  202. end;
  203.  
  204. function TPlayGroundMatrix.CoordToIndex(x, y: integer): integer;
  205. var
  206.   c: TCoord;
  207. begin
  208.   c.X := x;
  209.   c.Y := y;
  210.   result := CoordToIndex(c);
  211. end;
  212.  
  213. function TPlayGroundMatrix.CoordToIndex(coord: TCoord): integer;
  214. begin
  215.   result := coord.X + coord.Y * Width;
  216. end;
  217.  
  218. class function TPlayGroundMatrix.FieldState(t: TFieldType): TFieldState;
  219. begin
  220.   result := fsError;
  221.   case t of
  222.     ftFullSpace: result := fsLocked;
  223.     ftEmpty:     result := fsAvailable;
  224.     ftGreen:     result := fsStone;
  225.     ftYellow:    result := fsStone;
  226.     ftRed:       result := fsStone;
  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 := fsError;
  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) = fsStone) then result := true;
  255.     if (SourceX-2 = DestX) and (SourceY+2 = DestY) and (FieldState(SourceX-1, SourceY+1) = fsStone) then result := true;
  256.     if (SourceX+2 = DestX) and (SourceY-2 = DestY) and (FieldState(SourceX+1, SourceY-1) = fsStone) then result := true;
  257.     if (SourceX+2 = DestX) and (SourceY+2 = DestY) and (FieldState(SourceX+1, SourceY+1) = fsStone) then result := true;
  258.   end;
  259.  
  260.   if (SourceX+2 = DestX) and (SourceY   = DestY) and (FieldState(SourceX+1, SourceY  ) = fsStone) then result := true;
  261.   if (SourceX-2 = DestX) and (SourceY   = DestY) and (FieldState(SourceX-1, SourceY  ) = fsStone) then result := true;
  262.   if (SourceX   = DestX) and (SourceY+2 = DestY) and (FieldState(SourceX  , SourceY+1) = fsStone) then result := true;
  263.   if (SourceX   = DestX) and (SourceY-2 = DestY) and (FieldState(SourceX  , SourceY-1) = fsStone) then result := true;
  264. end;
  265.  
  266. function TPlayGroundMatrix.CanJump(SourceX, SourceY: integer; DiagonalOK: boolean): boolean;
  267. begin
  268.   if FieldState(SourceX, SourceY) <> fsStone 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. function TLevel.LevelStringToLevelArray(ShowErrors: boolean): TLevelArray;
  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. begin
  365.   // Zuerst nach Fehlern suchen
  366.   err := CheckLevelIntegrity(ShowErrors);
  367.   if err <> leNone then exit;
  368.  
  369.   // Nun Matrix aufbauen
  370.   SetLength(result, 0);
  371.   for i := NUM_HEADERS to FStringList.Count-1 do
  372.   begin
  373.     y := i - NUM_HEADERS;
  374.  
  375.     SetLength(result, Length(result)+1); // add line to matrix
  376.  
  377.     Line := FStringList.Strings[i];
  378.     result[y].Indent := DotsAtBeginning(Line) - DotsAtEnd(Line);
  379.     Line := StringReplace(Line, '.', '', [rfReplaceAll]);
  380.     SetLength(result[y].Fields, Length(Line));
  381.  
  382.     for x := 0 to Length(Line)-1 do
  383.     begin
  384.       ch := Line[x+1];
  385.       lch := LowerCase(ch)[1];
  386.       uch := UpperCase(ch)[1];
  387.  
  388.       t := ftUndefined;
  389.       case lch of
  390.         '*': t := ftFullSpace;
  391.         'e': t := ftEmpty;
  392.         'r': t := ftRed;
  393.         'y': t := ftYellow;
  394.         'g': t := ftGreen;
  395.       end;
  396.  
  397.       result[y].Fields[x].Typ := t;
  398.       result[y].Fields[x].Goal := (ch = uch) and (ch <> lch);
  399.     end;
  400.   end;
  401. end;
  402.  
  403. function TLevel.CheckLevelIntegrity(ShowErrors: boolean): TLevelError;
  404. resourcestring
  405.   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 *.';
  406.   LNG_LVL_UNSUPPORTED_VERSION = 'Level format invalid: Version not supported.';
  407.   LNG_LVL_UNSUPPORTED_MODE = 'Level format invalid: Mode not supported.';
  408.   LNG_LVL_EMPTY_BOARD = 'Level invalid: Board is empty.';
  409.   LNG_LVL_INVALID_LENGTH = 'Level invalid: Lines don''t have an equal amount of elements.';
  410. begin
  411.   result := CheckLevelIntegrity;
  412.   if ShowErrors then
  413.   begin
  414.     case result of
  415.       leNone: ;
  416.       leInvalidElement: MessageDlg(LNG_LVL_INVALID_ELEMENT, mtError, [mbOk], 0);
  417.       leUnsupportedVersion: MessageDlg(LNG_LVL_UNSUPPORTED_VERSION, mtError, [mbOk], 0);
  418.       leUnsupportedMode: MessageDlg(LNG_LVL_UNSUPPORTED_MODE, mtError, [mbOk], 0);
  419.       leEmptyBoard: MessageDlg(LNG_LVL_EMPTY_BOARD, mtError, [mbOk], 0);
  420.       leRowInvalidLength: MessageDlg(LNG_LVL_INVALID_LENGTH, mtError, [mbOk], 0);
  421.     end;
  422.   end;
  423. end;
  424.  
  425. function TLevel.CheckLevelIntegrity: TLevelError;
  426. var
  427.   tmp: string;
  428.   i: Integer;
  429.   Line: string;
  430.   firstLine: string;
  431.   thisLine: string;
  432. begin
  433.   result := leNone;
  434.  
  435.   // Check 1: Ist der Header OK?
  436.  
  437.   if LowerCase(FStringList.Strings[0]) <> 'version 2' then
  438.   begin
  439.     result := leUnsupportedVersion;
  440.     exit;
  441.   end;
  442.  
  443.   if ((LowerCase(FStringList.Strings[1]) <> 'mode: normal') and (LowerCase(FStringList.Strings[1]) <> 'mode: diagonal')) then
  444.   begin
  445.     result := leUnsupportedMode;
  446.     exit;
  447.   end;
  448.  
  449.   // Check 2: Ist das Brett leer?
  450.  
  451.   tmp := '';
  452.   for i := NUM_HEADERS to FStringList.Count-1 do tmp := tmp + FStringList.Strings[i];
  453.   if Trim(StringReplace(tmp, '.', '', [rfReplaceAll])) = '' then
  454.   begin
  455.     result := leEmptyBoard;
  456.     exit;
  457.   end;
  458.  
  459.   // Check 3: Geht das Level nicht in einem Quadrat oder Rechteck auf?
  460.  
  461.   firstLine := StringReplace(FStringList.Strings[NUM_HEADERS], '.', '', [rfReplaceAll]);
  462.   for i := NUM_HEADERS to FStringList.Count-1 do
  463.   begin
  464.     thisLine := StringReplace(FStringList.Strings[i], '.', '', [rfReplaceAll]);
  465.     if Length(thisLine) <> Length(firstLine) then
  466.     begin
  467.       result := leRowInvalidLength; // at row y-NUM_HEADERS
  468.       exit;
  469.     end;
  470.   end;
  471.  
  472.   // Check 4: Gibt es ungültige Elemente in den Zeilen?
  473.  
  474.   for i := NUM_HEADERS to FStringList.Count-1 do
  475.   begin
  476.     Line := FStringList.Strings[i];
  477.  
  478.     Line := StringReplace(Line, '.', '', [rfReplaceAll]);
  479.     Line := StringReplace(Line, '*', '', [rfReplaceAll]);
  480.     Line := StringReplace(Line, 'r', '', [rfReplaceAll, rfIgnoreCase]);
  481.     Line := StringReplace(Line, 'y', '', [rfReplaceAll, rfIgnoreCase]);
  482.     Line := StringReplace(Line, 'g', '', [rfReplaceAll, rfIgnoreCase]);
  483.     Line := StringReplace(Line, 'e', '', [rfReplaceAll, rfIgnoreCase]);
  484.  
  485.     if Length(Line) > 0 then
  486.     begin
  487.       result := leInvalidElement; // at row y-NUM_HEADERS
  488.       Exit;
  489.     end;
  490.   end;
  491.  
  492.   // Check 5: Kann im Level gesprungen werden?
  493.  
  494.   { Wird hier nicht abgeprüft, da dafür zuerst der PlayGround gebaut sein muss.
  495.     Es ist außerdem eher ein logischer Fehler, kein Fehler in der Levelstruktur! }
  496. end;
  497.  
  498. end.
  499.