Subversion Repositories jumper

Rev

Rev 21 | Rev 23 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 daniel-mar 1
unit LevelFunctions;
2
 
3
interface
4
 
5
uses
21 daniel-mar 6
  SysUtils, Dialogs, Functions, ExtCtrls, Classes, Math;
1 daniel-mar 7
 
8
type
19 daniel-mar 9
  TFieldType = (ftUndefined, ftFullSpace, ftEmpty, ftRed, ftYellow, ftGreen);
1 daniel-mar 10
 
11
  TFieldProperties = record
12
    Typ: TFieldType;
13
    Goal: Boolean;
14
  end;
15
 
11 daniel-mar 16
  TGameMode = (gmUndefined, gmNormal, gmDiagonal);
1 daniel-mar 17
 
19 daniel-mar 18
  TRow = record
19
    Indent: integer;
20
    Fields: array of TFieldProperties;
21
  end;
22
  TLevelArray = array of TRow;
1 daniel-mar 23
 
19 daniel-mar 24
  TLevelError = (leUndefined, leNone, leInvalidElement, leEmptyBoard, leRowInvalidLength,
11 daniel-mar 25
                 leUnsupportedVersion, leUnsupportedMode);
1 daniel-mar 26
 
11 daniel-mar 27
  TLevel = class(TObject)
28
  private
29
    FStringList: TStringList;
30
    procedure Load(ABoardFile: string);
22 daniel-mar 31
    function GetGameMode: TGameMode;
11 daniel-mar 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;
22 daniel-mar 38
    property GameMode: TGameMode read GetGameMode;
11 daniel-mar 39
  end;
40
 
21 daniel-mar 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;
22 daniel-mar 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;
21 daniel-mar 66
  end;
67
 
8 daniel-mar 68
function FieldTypeWorth(t: TFieldType): integer;
1 daniel-mar 69
 
70
implementation
71
 
21 daniel-mar 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;
1 daniel-mar 83
var
22 daniel-mar 84
  x, y: integer;
1 daniel-mar 85
begin
21 daniel-mar 86
  result := false;
22 daniel-mar 87
  for x := Low(Fields) to High(Fields) do
21 daniel-mar 88
  begin
22 daniel-mar 89
    for y := Low(Fields[x]) to High(Fields[x]) do
21 daniel-mar 90
    begin
22 daniel-mar 91
      result := result or Fields[x][y].Goal;
21 daniel-mar 92
    end;
93
  end;
94
end;
1 daniel-mar 95
 
21 daniel-mar 96
function TPlayGroundMatrix.GoalFieldType: TFieldType;
97
var
22 daniel-mar 98
  x, y: integer;
21 daniel-mar 99
begin
100
  result := ftEmpty; // Damit der Compiler nicht meckert
22 daniel-mar 101
  for x := Low(Fields) to High(Fields) do
21 daniel-mar 102
  begin
22 daniel-mar 103
    for y := Low(Fields[x]) to High(Fields[x]) do
21 daniel-mar 104
    begin
22 daniel-mar 105
      if Fields[x][y].Goal then result := Fields[x][y].FieldType
21 daniel-mar 106
    end;
107
  end;
108
end;
1 daniel-mar 109
 
21 daniel-mar 110
function TPlayGroundMatrix.MatrixWorth: integer;
111
var
22 daniel-mar 112
  x, y: integer;
21 daniel-mar 113
begin
114
  result := 0;
22 daniel-mar 115
  for x := Low(Fields) to High(Fields) do
21 daniel-mar 116
  begin
22 daniel-mar 117
    for y := Low(Fields[x]) to High(Fields[x]) do
21 daniel-mar 118
    begin
22 daniel-mar 119
      Inc(result, FieldTypeWorth(Fields[x][y].FieldType));
21 daniel-mar 120
    end;
121
  end;
122
end;
1 daniel-mar 123
 
21 daniel-mar 124
procedure TPlayGroundMatrix.ClearMatrix(FreeVCL: boolean);
125
var
22 daniel-mar 126
  x, y: integer;
21 daniel-mar 127
begin
22 daniel-mar 128
  for x := Low(Fields) to High(Fields) do
1 daniel-mar 129
  begin
22 daniel-mar 130
    for y := Low(Fields[x]) to High(Fields[x]) do
1 daniel-mar 131
    begin
21 daniel-mar 132
      if FreeVCL then
133
      begin
22 daniel-mar 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;
1 daniel-mar 136
      end;
21 daniel-mar 137
    end;
22 daniel-mar 138
    SetLength(Fields[x], 0);
21 daniel-mar 139
  end;
140
  SetLength(Fields, 0);
141
end;
1 daniel-mar 142
 
21 daniel-mar 143
function TPlayGroundMatrix.CloneMatrix: TPlayGroundMatrix;
144
var
22 daniel-mar 145
  x, y: integer;
21 daniel-mar 146
begin
147
  SetLength(result.Fields, Length(Fields));
22 daniel-mar 148
  for x := Low(Fields) to High(Fields) do
21 daniel-mar 149
  begin
22 daniel-mar 150
    SetLength(result.Fields[x], Length(Fields[x]));
151
    for y := Low(Fields[x]) to High(Fields[x]) do
21 daniel-mar 152
    begin
22 daniel-mar 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;
1 daniel-mar 157
    end;
158
  end;
159
end;
160
 
21 daniel-mar 161
function TPlayGroundMatrix.FieldState(t: TFieldType): TFieldState;
1 daniel-mar 162
begin
21 daniel-mar 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;
1 daniel-mar 171
end;
172
 
21 daniel-mar 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
 
22 daniel-mar 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
 
11 daniel-mar 254
{ TLevel }
255
 
256
const NUM_HEADERS = 2;
257
 
258
constructor TLevel.Create(ABoardFile: string);
1 daniel-mar 259
begin
11 daniel-mar 260
  inherited Create;
261
  FStringList := TStringList.Create;
262
  Load(ABoardFile);
1 daniel-mar 263
end;
264
 
11 daniel-mar 265
destructor TLevel.Destroy;
1 daniel-mar 266
begin
11 daniel-mar 267
  FreeAndNil(FStringList);
268
 
269
  inherited;
1 daniel-mar 270
end;
271
 
11 daniel-mar 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);
1 daniel-mar 283
var
11 daniel-mar 284
  i: Integer;
1 daniel-mar 285
begin
11 daniel-mar 286
  FStringList.Clear;
287
  FStringList.LoadFromFile(ABoardFile);
1 daniel-mar 288
 
11 daniel-mar 289
  // Remove whitespaces and empty lines
290
  for i := FStringList.Count-1 downto NUM_HEADERS do
1 daniel-mar 291
  begin
11 daniel-mar 292
    FStringList.Strings[i] := StringReplace(FStringList.Strings[i], ' ', '', [rfReplaceAll]);
293
    if FStringList.Strings[i] = '' then FStringList.Delete(i);
1 daniel-mar 294
  end;
11 daniel-mar 295
end;
1 daniel-mar 296
 
11 daniel-mar 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
1 daniel-mar 315
  begin
11 daniel-mar 316
    y := i - NUM_HEADERS;
1 daniel-mar 317
 
11 daniel-mar 318
    SetLength(result, Length(result)+1); // add line to matrix
1 daniel-mar 319
 
11 daniel-mar 320
    Line := FStringList.Strings[i];
19 daniel-mar 321
    result[y].Indent := DotsAtBeginning(Line) - DotsAtEnd(Line);
322
    Line := StringReplace(Line, '.', '', [rfReplaceAll]);
323
    SetLength(result[y].Fields, Length(Line));
1 daniel-mar 324
 
19 daniel-mar 325
    for x := 0 to Length(Line)-1 do
11 daniel-mar 326
    begin
19 daniel-mar 327
      ch := Line[x+1];
11 daniel-mar 328
      lch := LowerCase(ch)[1];
329
      uch := UpperCase(ch)[1];
19 daniel-mar 330
 
331
      t := ftUndefined;
11 daniel-mar 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;
1 daniel-mar 339
 
19 daniel-mar 340
      result[y].Fields[x].Typ := t;
341
      result[y].Fields[x].Goal := (ch = uch) and (ch <> lch);
11 daniel-mar 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
1 daniel-mar 356
  begin
11 daniel-mar 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);
1 daniel-mar 364
    end;
365
  end;
11 daniel-mar 366
end;
1 daniel-mar 367
 
11 daniel-mar 368
function TLevel.CheckLevelIntegrity: TLevelError;
369
var
19 daniel-mar 370
  tmp: string;
11 daniel-mar 371
  i: Integer;
372
  Line: string;
19 daniel-mar 373
  firstLine: string;
374
  thisLine: string;
11 daniel-mar 375
begin
376
  result := leNone;
1 daniel-mar 377
 
11 daniel-mar 378
  // Check 1: Ist der Header OK?
1 daniel-mar 379
 
11 daniel-mar 380
  if LowerCase(FStringList.Strings[0]) <> 'version 2' then
1 daniel-mar 381
  begin
11 daniel-mar 382
    result := leUnsupportedVersion;
383
    exit;
1 daniel-mar 384
  end;
385
 
11 daniel-mar 386
  if ((LowerCase(FStringList.Strings[1]) <> 'mode: normal') and (LowerCase(FStringList.Strings[1]) <> 'mode: diagonal')) then
1 daniel-mar 387
  begin
11 daniel-mar 388
    result := leUnsupportedMode;
389
    exit;
1 daniel-mar 390
  end;
391
 
11 daniel-mar 392
  // Check 2: Ist das Brett leer?
1 daniel-mar 393
 
19 daniel-mar 394
  tmp := '';
395
  for i := NUM_HEADERS to FStringList.Count-1 do tmp := tmp + FStringList.Strings[i];
396
  if Trim(StringReplace(tmp, '.', '', [rfReplaceAll])) = '' then
1 daniel-mar 397
  begin
11 daniel-mar 398
    result := leEmptyBoard;
399
    exit;
1 daniel-mar 400
  end;
401
 
11 daniel-mar 402
  // Check 3: Geht das Level nicht in einem Quadrat oder Rechteck auf?
1 daniel-mar 403
 
19 daniel-mar 404
  firstLine := StringReplace(FStringList.Strings[NUM_HEADERS], '.', '', [rfReplaceAll]);
11 daniel-mar 405
  for i := NUM_HEADERS to FStringList.Count-1 do
1 daniel-mar 406
  begin
19 daniel-mar 407
    thisLine := StringReplace(FStringList.Strings[i], '.', '', [rfReplaceAll]);
408
    if Length(thisLine) <> Length(firstLine) then
11 daniel-mar 409
    begin
410
      result := leRowInvalidLength; // at row y-NUM_HEADERS
411
      exit;
412
    end;
1 daniel-mar 413
  end;
414
 
11 daniel-mar 415
  // Check 4: Gibt es ungültige Elemente in den Zeilen?
1 daniel-mar 416
 
11 daniel-mar 417
  for i := NUM_HEADERS to FStringList.Count-1 do
418
  begin
419
    Line := FStringList.Strings[i];
1 daniel-mar 420
 
11 daniel-mar 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]);
1 daniel-mar 427
 
11 daniel-mar 428
    if Length(Line) > 0 then
1 daniel-mar 429
    begin
11 daniel-mar 430
      result := leInvalidElement; // at row y-NUM_HEADERS
431
      Exit;
1 daniel-mar 432
    end;
433
  end;
434
 
22 daniel-mar 435
  // Check 5: Kann im Level gesprungen werden?
11 daniel-mar 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! }
8 daniel-mar 439
end;
440
 
1 daniel-mar 441
end.