Subversion Repositories jumper

Rev

Rev 19 | Rev 22 | 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);
31
  public
32
    constructor Create(ABoardFile: string);
33
    destructor Destroy; override;
34
    function LevelStringToLevelArray(ShowErrors: boolean): TLevelArray;
35
    function CheckLevelIntegrity: TLevelError; overload;
36
    function CheckLevelIntegrity(ShowErrors: boolean): TLevelError; overload;
37
    function GetGameMode: TGameMode;
38
  end;
39
 
21 daniel-mar 40
  TField = record
41
    FieldType: TFieldType;
42
    Goal: Boolean;
43
    Panel: TPanel;
44
    Stone: TImage;
45
  end;
46
 
47
  TGoalStatus = (gsUndefined, gsNoGoal, gsMultipleStonesRemaining, gsLastStoneInGoalRed, gsLastStoneInGoalYellow, gsLastStoneInGoalGreen, gsLastStoneOutsideGoal);
48
 
49
  TFieldState = (fsUndefined, fsError, fsLocked, fsAvailable, fsStone);
50
 
51
  TPlayGroundMatrix = record
52
    Fields: array of array of TField;
53
  public
54
    function MatrixHasGoal: boolean;
55
    function GoalFieldType: TFieldType;
56
    function MatrixWorth: integer;
57
    procedure ClearMatrix(FreeVCL: boolean);
58
    function CloneMatrix: TPlayGroundMatrix;
59
    function FieldState(t: TFieldType): TFieldState; overload;
60
    function FieldState(f: TField): TFieldState; overload;
61
    function FieldState(x, y: integer): TFieldState; overload;
62
  end;
63
 
8 daniel-mar 64
function FieldTypeWorth(t: TFieldType): integer;
1 daniel-mar 65
 
66
implementation
67
 
21 daniel-mar 68
function FieldTypeWorth(t: TFieldType): integer;
69
begin
70
  if t = ftGreen then result := 10
71
  else if t = ftYellow then result := 20
72
  else if t = ftRed then result := 30
73
  else result := 0;
74
end;
75
 
76
{ TPlayGroundMatrix }
77
 
78
function TPlayGroundMatrix.MatrixHasGoal: boolean;
1 daniel-mar 79
var
21 daniel-mar 80
  i, j: integer;
1 daniel-mar 81
begin
21 daniel-mar 82
  result := false;
83
  for i := Low(Fields) to High(Fields) do
84
  begin
85
    for j := Low(Fields[i]) to High(Fields[i]) do
86
    begin
87
      result := result or Fields[i][j].Goal;
88
    end;
89
  end;
90
end;
1 daniel-mar 91
 
21 daniel-mar 92
function TPlayGroundMatrix.GoalFieldType: TFieldType;
93
var
94
  i, j: integer;
95
begin
96
  result := ftEmpty; // Damit der Compiler nicht meckert
97
  for i := Low(Fields) to High(Fields) do
98
  begin
99
    for j := Low(Fields[i]) to High(Fields[i]) do
100
    begin
101
      if Fields[i][j].Goal then result := Fields[i][j].FieldType
102
    end;
103
  end;
104
end;
1 daniel-mar 105
 
21 daniel-mar 106
function TPlayGroundMatrix.MatrixWorth: integer;
107
var
108
  i, j: integer;
109
begin
110
  result := 0;
111
  for i := Low(Fields) to High(Fields) do
112
  begin
113
    for j := Low(Fields[i]) to High(Fields[i]) do
114
    begin
115
      Inc(result, FieldTypeWorth(Fields[i][j].FieldType));
116
    end;
117
  end;
118
end;
1 daniel-mar 119
 
21 daniel-mar 120
procedure TPlayGroundMatrix.ClearMatrix(FreeVCL: boolean);
121
var
122
  i, j: integer;
123
begin
124
  for i := Low(Fields) to High(Fields) do
1 daniel-mar 125
  begin
21 daniel-mar 126
    for j := Low(Fields[i]) to High(Fields[i]) do
1 daniel-mar 127
    begin
21 daniel-mar 128
      if FreeVCL then
129
      begin
130
        if Assigned(Fields[i][j].Stone) then Fields[i][j].Stone.Free;
131
        if Assigned(Fields[i][j].Panel) then Fields[i][j].Panel.Free;
1 daniel-mar 132
      end;
21 daniel-mar 133
    end;
134
    SetLength(Fields[i], 0);
135
  end;
136
  SetLength(Fields, 0);
137
end;
1 daniel-mar 138
 
21 daniel-mar 139
function TPlayGroundMatrix.CloneMatrix: TPlayGroundMatrix;
140
var
141
  i, j: integer;
142
begin
143
  SetLength(result.Fields, Length(Fields));
144
  for i := Low(Fields) to High(Fields) do
145
  begin
146
    SetLength(result.Fields[i], Length(Fields[i]));
147
    for j := Low(Fields[i]) to High(Fields[i]) do
148
    begin
149
      result.Fields[i][j].FieldType := Fields[i][j].FieldType;
150
      result.Fields[i][j].Goal      := Fields[i][j].Goal;
151
      result.Fields[i][j].Panel     := Fields[i][j].Panel;
152
      result.Fields[i][j].Stone     := Fields[i][j].Stone;
1 daniel-mar 153
    end;
154
  end;
155
end;
156
 
21 daniel-mar 157
function TPlayGroundMatrix.FieldState(t: TFieldType): TFieldState;
1 daniel-mar 158
begin
21 daniel-mar 159
  result := fsError;
160
  case t of
161
    ftFullSpace: result := fsLocked;
162
    ftEmpty:     result := fsAvailable;
163
    ftGreen:     result := fsStone;
164
    ftYellow:    result := fsStone;
165
    ftRed:       result := fsStone;
166
  end;
1 daniel-mar 167
end;
168
 
21 daniel-mar 169
function TPlayGroundMatrix.FieldState(f: TField): TFieldState;
170
begin
171
  result := FieldState(f.FieldType);
172
end;
173
 
174
function TPlayGroundMatrix.FieldState(x, y: integer): TFieldState;
175
begin
176
  result := fsError;
177
  if (x < Low(Fields)) or (x > High(Fields)) then exit;
178
  if (y < Low(Fields[x])) or (y > High(Fields[x])) then exit;
179
 
180
  result := FieldState(Fields[x][y]);
181
end;
182
 
11 daniel-mar 183
{ TLevel }
184
 
185
const NUM_HEADERS = 2;
186
 
187
constructor TLevel.Create(ABoardFile: string);
1 daniel-mar 188
begin
11 daniel-mar 189
  inherited Create;
190
  FStringList := TStringList.Create;
191
  Load(ABoardFile);
1 daniel-mar 192
end;
193
 
11 daniel-mar 194
destructor TLevel.Destroy;
1 daniel-mar 195
begin
11 daniel-mar 196
  FreeAndNil(FStringList);
197
 
198
  inherited;
1 daniel-mar 199
end;
200
 
11 daniel-mar 201
function TLevel.GetGameMode: TGameMode;
202
begin
203
  if LowerCase(FStringList.Strings[1]) = 'mode: normal' then
204
    result := gmNormal
205
  else if LowerCase(FStringList.Strings[1]) = 'mode: diagonal' then
206
    result := gmDiagonal
207
  else
208
    result := gmUndefined;
209
end;
210
 
211
procedure TLevel.Load(ABoardFile: string);
1 daniel-mar 212
var
11 daniel-mar 213
  i: Integer;
1 daniel-mar 214
begin
11 daniel-mar 215
  FStringList.Clear;
216
  FStringList.LoadFromFile(ABoardFile);
1 daniel-mar 217
 
11 daniel-mar 218
  // Remove whitespaces and empty lines
219
  for i := FStringList.Count-1 downto NUM_HEADERS do
1 daniel-mar 220
  begin
11 daniel-mar 221
    FStringList.Strings[i] := StringReplace(FStringList.Strings[i], ' ', '', [rfReplaceAll]);
222
    if FStringList.Strings[i] = '' then FStringList.Delete(i);
1 daniel-mar 223
  end;
11 daniel-mar 224
end;
1 daniel-mar 225
 
11 daniel-mar 226
function TLevel.LevelStringToLevelArray(ShowErrors: boolean): TLevelArray;
227
var
228
  i: integer;
229
  t: TFieldType;
230
  err: TLevelError;
231
  y: Integer;
232
  x: Integer;
233
  Line: string;
234
  lch, uch: char;
235
  ch: char;
236
begin
237
  // Zuerst nach Fehlern suchen
238
  err := CheckLevelIntegrity(ShowErrors);
239
  if err <> leNone then exit;
240
 
241
  // Nun Matrix aufbauen
242
  SetLength(result, 0);
243
  for i := NUM_HEADERS to FStringList.Count-1 do
1 daniel-mar 244
  begin
11 daniel-mar 245
    y := i - NUM_HEADERS;
1 daniel-mar 246
 
11 daniel-mar 247
    SetLength(result, Length(result)+1); // add line to matrix
1 daniel-mar 248
 
11 daniel-mar 249
    Line := FStringList.Strings[i];
19 daniel-mar 250
    result[y].Indent := DotsAtBeginning(Line) - DotsAtEnd(Line);
251
    Line := StringReplace(Line, '.', '', [rfReplaceAll]);
252
    SetLength(result[y].Fields, Length(Line));
1 daniel-mar 253
 
19 daniel-mar 254
    for x := 0 to Length(Line)-1 do
11 daniel-mar 255
    begin
19 daniel-mar 256
      ch := Line[x+1];
11 daniel-mar 257
      lch := LowerCase(ch)[1];
258
      uch := UpperCase(ch)[1];
19 daniel-mar 259
 
260
      t := ftUndefined;
11 daniel-mar 261
      case lch of
262
        '*': t := ftFullSpace;
263
        'e': t := ftEmpty;
264
        'r': t := ftRed;
265
        'y': t := ftYellow;
266
        'g': t := ftGreen;
267
      end;
1 daniel-mar 268
 
19 daniel-mar 269
      result[y].Fields[x].Typ := t;
270
      result[y].Fields[x].Goal := (ch = uch) and (ch <> lch);
11 daniel-mar 271
    end;
272
  end;
273
end;
274
 
275
function TLevel.CheckLevelIntegrity(ShowErrors: boolean): TLevelError;
276
resourcestring
277
  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 *.';
278
  LNG_LVL_UNSUPPORTED_VERSION = 'Level format invalid: Version not supported.';
279
  LNG_LVL_UNSUPPORTED_MODE = 'Level format invalid: Mode not supported.';
280
  LNG_LVL_EMPTY_BOARD = 'Level invalid: Board is empty.';
281
  LNG_LVL_INVALID_LENGTH = 'Level invalid: Lines don''t have an equal amount of elements.';
282
begin
283
  result := CheckLevelIntegrity;
284
  if ShowErrors then
1 daniel-mar 285
  begin
11 daniel-mar 286
    case result of
287
      leNone: ;
288
      leInvalidElement: MessageDlg(LNG_LVL_INVALID_ELEMENT, mtError, [mbOk], 0);
289
      leUnsupportedVersion: MessageDlg(LNG_LVL_UNSUPPORTED_VERSION, mtError, [mbOk], 0);
290
      leUnsupportedMode: MessageDlg(LNG_LVL_UNSUPPORTED_MODE, mtError, [mbOk], 0);
291
      leEmptyBoard: MessageDlg(LNG_LVL_EMPTY_BOARD, mtError, [mbOk], 0);
292
      leRowInvalidLength: MessageDlg(LNG_LVL_INVALID_LENGTH, mtError, [mbOk], 0);
1 daniel-mar 293
    end;
294
  end;
11 daniel-mar 295
end;
1 daniel-mar 296
 
11 daniel-mar 297
function TLevel.CheckLevelIntegrity: TLevelError;
298
var
19 daniel-mar 299
  tmp: string;
11 daniel-mar 300
  i: Integer;
301
  Line: string;
19 daniel-mar 302
  firstLine: string;
303
  thisLine: string;
11 daniel-mar 304
begin
305
  result := leNone;
1 daniel-mar 306
 
11 daniel-mar 307
  // Check 1: Ist der Header OK?
1 daniel-mar 308
 
11 daniel-mar 309
  if LowerCase(FStringList.Strings[0]) <> 'version 2' then
1 daniel-mar 310
  begin
11 daniel-mar 311
    result := leUnsupportedVersion;
312
    exit;
1 daniel-mar 313
  end;
314
 
11 daniel-mar 315
  if ((LowerCase(FStringList.Strings[1]) <> 'mode: normal') and (LowerCase(FStringList.Strings[1]) <> 'mode: diagonal')) then
1 daniel-mar 316
  begin
11 daniel-mar 317
    result := leUnsupportedMode;
318
    exit;
1 daniel-mar 319
  end;
320
 
11 daniel-mar 321
  // Check 2: Ist das Brett leer?
1 daniel-mar 322
 
19 daniel-mar 323
  tmp := '';
324
  for i := NUM_HEADERS to FStringList.Count-1 do tmp := tmp + FStringList.Strings[i];
325
  if Trim(StringReplace(tmp, '.', '', [rfReplaceAll])) = '' then
1 daniel-mar 326
  begin
11 daniel-mar 327
    result := leEmptyBoard;
328
    exit;
1 daniel-mar 329
  end;
330
 
11 daniel-mar 331
  // Check 3: Geht das Level nicht in einem Quadrat oder Rechteck auf?
1 daniel-mar 332
 
19 daniel-mar 333
  firstLine := StringReplace(FStringList.Strings[NUM_HEADERS], '.', '', [rfReplaceAll]);
11 daniel-mar 334
  for i := NUM_HEADERS to FStringList.Count-1 do
1 daniel-mar 335
  begin
19 daniel-mar 336
    thisLine := StringReplace(FStringList.Strings[i], '.', '', [rfReplaceAll]);
337
    if Length(thisLine) <> Length(firstLine) then
11 daniel-mar 338
    begin
339
      result := leRowInvalidLength; // at row y-NUM_HEADERS
340
      exit;
341
    end;
1 daniel-mar 342
  end;
343
 
11 daniel-mar 344
  // Check 4: Gibt es ungültige Elemente in den Zeilen?
1 daniel-mar 345
 
11 daniel-mar 346
  for i := NUM_HEADERS to FStringList.Count-1 do
347
  begin
348
    Line := FStringList.Strings[i];
1 daniel-mar 349
 
11 daniel-mar 350
    Line := StringReplace(Line, '.', '', [rfReplaceAll]);
351
    Line := StringReplace(Line, '*', '', [rfReplaceAll]);
352
    Line := StringReplace(Line, 'r', '', [rfReplaceAll, rfIgnoreCase]);
353
    Line := StringReplace(Line, 'y', '', [rfReplaceAll, rfIgnoreCase]);
354
    Line := StringReplace(Line, 'g', '', [rfReplaceAll, rfIgnoreCase]);
355
    Line := StringReplace(Line, 'e', '', [rfReplaceAll, rfIgnoreCase]);
1 daniel-mar 356
 
11 daniel-mar 357
    if Length(Line) > 0 then
1 daniel-mar 358
    begin
11 daniel-mar 359
      result := leInvalidElement; // at row y-NUM_HEADERS
360
      Exit;
1 daniel-mar 361
    end;
362
  end;
363
 
11 daniel-mar 364
  // Check 5: Kann im Level gesprungen werden
365
 
366
  { Wird hier nicht abgeprüft, da dafür zuerst der PlayGround gebaut sein muss.
367
    Es ist außerdem eher ein logischer Fehler, kein Fehler in der Levelstruktur! }
8 daniel-mar 368
end;
369
 
1 daniel-mar 370
end.