Subversion Repositories jumper

Rev

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