Subversion Repositories jumper

Rev

Rev 22 | Rev 24 | 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
23 daniel-mar 9
  TCoord = record
10
    X: integer;
11
    Y: integer;
12
  end;
13
 
19 daniel-mar 14
  TFieldType = (ftUndefined, ftFullSpace, ftEmpty, ftRed, ftYellow, ftGreen);
1 daniel-mar 15
 
16
  TFieldProperties = record
17
    Typ: TFieldType;
18
    Goal: Boolean;
19
  end;
20
 
11 daniel-mar 21
  TGameMode = (gmUndefined, gmNormal, gmDiagonal);
1 daniel-mar 22
 
19 daniel-mar 23
  TRow = record
24
    Indent: integer;
25
    Fields: array of TFieldProperties;
26
  end;
27
  TLevelArray = array of TRow;
1 daniel-mar 28
 
19 daniel-mar 29
  TLevelError = (leUndefined, leNone, leInvalidElement, leEmptyBoard, leRowInvalidLength,
11 daniel-mar 30
                 leUnsupportedVersion, leUnsupportedMode);
1 daniel-mar 31
 
11 daniel-mar 32
  TLevel = class(TObject)
33
  private
34
    FStringList: TStringList;
35
    procedure Load(ABoardFile: string);
22 daniel-mar 36
    function GetGameMode: TGameMode;
11 daniel-mar 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;
22 daniel-mar 43
    property GameMode: TGameMode read GetGameMode;
11 daniel-mar 44
  end;
45
 
21 daniel-mar 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
23 daniel-mar 60
    procedure InitFieldArray(width, height: integer);
21 daniel-mar 61
    function MatrixHasGoal: boolean;
62
    function GoalFieldType: TFieldType;
63
    function MatrixWorth: integer;
64
    procedure ClearMatrix(FreeVCL: boolean);
65
    function CloneMatrix: TPlayGroundMatrix;
23 daniel-mar 66
    class function FieldState(t: TFieldType): TFieldState; overload; static;
21 daniel-mar 67
    function FieldState(f: TField): TFieldState; overload;
68
    function FieldState(x, y: integer): TFieldState; overload;
22 daniel-mar 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;
23 daniel-mar 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;
21 daniel-mar 77
  end;
78
 
8 daniel-mar 79
function FieldTypeWorth(t: TFieldType): integer;
1 daniel-mar 80
 
81
implementation
82
 
21 daniel-mar 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;
1 daniel-mar 94
var
22 daniel-mar 95
  x, y: integer;
1 daniel-mar 96
begin
21 daniel-mar 97
  result := false;
22 daniel-mar 98
  for x := Low(Fields) to High(Fields) do
21 daniel-mar 99
  begin
22 daniel-mar 100
    for y := Low(Fields[x]) to High(Fields[x]) do
21 daniel-mar 101
    begin
23 daniel-mar 102
      result := result or Fields[x,y].Goal;
21 daniel-mar 103
    end;
104
  end;
105
end;
1 daniel-mar 106
 
21 daniel-mar 107
function TPlayGroundMatrix.GoalFieldType: TFieldType;
108
var
22 daniel-mar 109
  x, y: integer;
21 daniel-mar 110
begin
111
  result := ftEmpty; // Damit der Compiler nicht meckert
22 daniel-mar 112
  for x := Low(Fields) to High(Fields) do
21 daniel-mar 113
  begin
22 daniel-mar 114
    for y := Low(Fields[x]) to High(Fields[x]) do
21 daniel-mar 115
    begin
23 daniel-mar 116
      if Fields[x,y].Goal then result := Fields[x,y].FieldType
21 daniel-mar 117
    end;
118
  end;
119
end;
1 daniel-mar 120
 
23 daniel-mar 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
 
21 daniel-mar 149
function TPlayGroundMatrix.MatrixWorth: integer;
150
var
22 daniel-mar 151
  x, y: integer;
21 daniel-mar 152
begin
153
  result := 0;
22 daniel-mar 154
  for x := Low(Fields) to High(Fields) do
21 daniel-mar 155
  begin
22 daniel-mar 156
    for y := Low(Fields[x]) to High(Fields[x]) do
21 daniel-mar 157
    begin
23 daniel-mar 158
      Inc(result, FieldTypeWorth(Fields[x,y].FieldType));
21 daniel-mar 159
    end;
160
  end;
161
end;
1 daniel-mar 162
 
23 daniel-mar 163
function TPlayGroundMatrix.Width: integer;
164
begin
165
  result := Length(Fields);
166
end;
167
 
21 daniel-mar 168
procedure TPlayGroundMatrix.ClearMatrix(FreeVCL: boolean);
169
var
22 daniel-mar 170
  x, y: integer;
21 daniel-mar 171
begin
22 daniel-mar 172
  for x := Low(Fields) to High(Fields) do
1 daniel-mar 173
  begin
22 daniel-mar 174
    for y := Low(Fields[x]) to High(Fields[x]) do
1 daniel-mar 175
    begin
21 daniel-mar 176
      if FreeVCL then
177
      begin
23 daniel-mar 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;
1 daniel-mar 180
      end;
21 daniel-mar 181
    end;
182
  end;
23 daniel-mar 183
  SetLength(Fields, 0, 0);
21 daniel-mar 184
end;
1 daniel-mar 185
 
21 daniel-mar 186
function TPlayGroundMatrix.CloneMatrix: TPlayGroundMatrix;
187
var
22 daniel-mar 188
  x, y: integer;
21 daniel-mar 189
begin
190
  SetLength(result.Fields, Length(Fields));
22 daniel-mar 191
  for x := Low(Fields) to High(Fields) do
21 daniel-mar 192
  begin
22 daniel-mar 193
    SetLength(result.Fields[x], Length(Fields[x]));
194
    for y := Low(Fields[x]) to High(Fields[x]) do
21 daniel-mar 195
    begin
23 daniel-mar 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;
1 daniel-mar 200
    end;
201
  end;
202
end;
203
 
23 daniel-mar 204
function TPlayGroundMatrix.CoordToIndex(x, y: integer): integer;
205
var
206
  c: TCoord;
1 daniel-mar 207
begin
23 daniel-mar 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
21 daniel-mar 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;
1 daniel-mar 228
end;
229
 
21 daniel-mar 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
 
23 daniel-mar 241
  result := FieldState(Fields[x,y]);
21 daniel-mar 242
end;
243
 
22 daniel-mar 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
 
11 daniel-mar 311
{ TLevel }
312
 
313
const NUM_HEADERS = 2;
314
 
315
constructor TLevel.Create(ABoardFile: string);
1 daniel-mar 316
begin
11 daniel-mar 317
  inherited Create;
318
  FStringList := TStringList.Create;
319
  Load(ABoardFile);
1 daniel-mar 320
end;
321
 
11 daniel-mar 322
destructor TLevel.Destroy;
1 daniel-mar 323
begin
11 daniel-mar 324
  FreeAndNil(FStringList);
325
 
326
  inherited;
1 daniel-mar 327
end;
328
 
11 daniel-mar 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);
1 daniel-mar 340
var
11 daniel-mar 341
  i: Integer;
1 daniel-mar 342
begin
11 daniel-mar 343
  FStringList.Clear;
344
  FStringList.LoadFromFile(ABoardFile);
1 daniel-mar 345
 
11 daniel-mar 346
  // Remove whitespaces and empty lines
347
  for i := FStringList.Count-1 downto NUM_HEADERS do
1 daniel-mar 348
  begin
11 daniel-mar 349
    FStringList.Strings[i] := StringReplace(FStringList.Strings[i], ' ', '', [rfReplaceAll]);
350
    if FStringList.Strings[i] = '' then FStringList.Delete(i);
1 daniel-mar 351
  end;
11 daniel-mar 352
end;
1 daniel-mar 353
 
11 daniel-mar 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
1 daniel-mar 372
  begin
11 daniel-mar 373
    y := i - NUM_HEADERS;
1 daniel-mar 374
 
11 daniel-mar 375
    SetLength(result, Length(result)+1); // add line to matrix
1 daniel-mar 376
 
11 daniel-mar 377
    Line := FStringList.Strings[i];
19 daniel-mar 378
    result[y].Indent := DotsAtBeginning(Line) - DotsAtEnd(Line);
379
    Line := StringReplace(Line, '.', '', [rfReplaceAll]);
380
    SetLength(result[y].Fields, Length(Line));
1 daniel-mar 381
 
19 daniel-mar 382
    for x := 0 to Length(Line)-1 do
11 daniel-mar 383
    begin
19 daniel-mar 384
      ch := Line[x+1];
11 daniel-mar 385
      lch := LowerCase(ch)[1];
386
      uch := UpperCase(ch)[1];
19 daniel-mar 387
 
388
      t := ftUndefined;
11 daniel-mar 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;
1 daniel-mar 396
 
19 daniel-mar 397
      result[y].Fields[x].Typ := t;
398
      result[y].Fields[x].Goal := (ch = uch) and (ch <> lch);
11 daniel-mar 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
1 daniel-mar 413
  begin
11 daniel-mar 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);
1 daniel-mar 421
    end;
422
  end;
11 daniel-mar 423
end;
1 daniel-mar 424
 
11 daniel-mar 425
function TLevel.CheckLevelIntegrity: TLevelError;
426
var
19 daniel-mar 427
  tmp: string;
11 daniel-mar 428
  i: Integer;
429
  Line: string;
19 daniel-mar 430
  firstLine: string;
431
  thisLine: string;
11 daniel-mar 432
begin
433
  result := leNone;
1 daniel-mar 434
 
11 daniel-mar 435
  // Check 1: Ist der Header OK?
1 daniel-mar 436
 
11 daniel-mar 437
  if LowerCase(FStringList.Strings[0]) <> 'version 2' then
1 daniel-mar 438
  begin
11 daniel-mar 439
    result := leUnsupportedVersion;
440
    exit;
1 daniel-mar 441
  end;
442
 
11 daniel-mar 443
  if ((LowerCase(FStringList.Strings[1]) <> 'mode: normal') and (LowerCase(FStringList.Strings[1]) <> 'mode: diagonal')) then
1 daniel-mar 444
  begin
11 daniel-mar 445
    result := leUnsupportedMode;
446
    exit;
1 daniel-mar 447
  end;
448
 
11 daniel-mar 449
  // Check 2: Ist das Brett leer?
1 daniel-mar 450
 
19 daniel-mar 451
  tmp := '';
452
  for i := NUM_HEADERS to FStringList.Count-1 do tmp := tmp + FStringList.Strings[i];
453
  if Trim(StringReplace(tmp, '.', '', [rfReplaceAll])) = '' then
1 daniel-mar 454
  begin
11 daniel-mar 455
    result := leEmptyBoard;
456
    exit;
1 daniel-mar 457
  end;
458
 
11 daniel-mar 459
  // Check 3: Geht das Level nicht in einem Quadrat oder Rechteck auf?
1 daniel-mar 460
 
19 daniel-mar 461
  firstLine := StringReplace(FStringList.Strings[NUM_HEADERS], '.', '', [rfReplaceAll]);
11 daniel-mar 462
  for i := NUM_HEADERS to FStringList.Count-1 do
1 daniel-mar 463
  begin
19 daniel-mar 464
    thisLine := StringReplace(FStringList.Strings[i], '.', '', [rfReplaceAll]);
465
    if Length(thisLine) <> Length(firstLine) then
11 daniel-mar 466
    begin
467
      result := leRowInvalidLength; // at row y-NUM_HEADERS
468
      exit;
469
    end;
1 daniel-mar 470
  end;
471
 
11 daniel-mar 472
  // Check 4: Gibt es ungültige Elemente in den Zeilen?
1 daniel-mar 473
 
11 daniel-mar 474
  for i := NUM_HEADERS to FStringList.Count-1 do
475
  begin
476
    Line := FStringList.Strings[i];
1 daniel-mar 477
 
11 daniel-mar 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]);
1 daniel-mar 484
 
11 daniel-mar 485
    if Length(Line) > 0 then
1 daniel-mar 486
    begin
11 daniel-mar 487
      result := leInvalidElement; // at row y-NUM_HEADERS
488
      Exit;
1 daniel-mar 489
    end;
490
  end;
491
 
22 daniel-mar 492
  // Check 5: Kann im Level gesprungen werden?
11 daniel-mar 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! }
8 daniel-mar 496
end;
497
 
1 daniel-mar 498
end.