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. |