Subversion Repositories jumper

Rev

Rev 25 | Blame | Compare with Previous | Last modification | View Log | RSS feed

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