Rev 11 | Rev 21 | 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 |
||
11 | daniel-mar | 6 | SysUtils, Dialogs, Functions, ExtCtrls, Graphics, 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 | |||
40 | procedure DrawLevelPreview(Level: TLevel; Image: TImage; BackgroundColor: TColor); |
||
8 | daniel-mar | 41 | function FieldTypeWorth(t: TFieldType): integer; |
1 | daniel-mar | 42 | |
43 | implementation |
||
44 | |||
11 | daniel-mar | 45 | procedure DrawLevelPreview(Level: TLevel; Image: TImage; BackgroundColor: TColor); |
1 | daniel-mar | 46 | var |
47 | LevelArray: TLevelArray; |
||
11 | daniel-mar | 48 | y, x: integer; |
1 | daniel-mar | 49 | t: TFieldType; |
19 | daniel-mar | 50 | indent: Integer; |
11 | daniel-mar | 51 | const |
52 | PREVIEW_BLOCK_SIZE = 10; // Enthält Field und Abstand |
||
53 | PREVIEW_TAB_SIZE = PREVIEW_BLOCK_SIZE div 2; // 5 |
||
1 | daniel-mar | 54 | begin |
55 | LevelArray := nil; |
||
56 | |||
57 | ClearImage(Image, BackgroundColor); |
||
58 | |||
11 | daniel-mar | 59 | LevelArray := Level.LevelStringToLevelArray(false); |
1 | daniel-mar | 60 | |
11 | daniel-mar | 61 | for y := Low(LevelArray) to High(LevelArray) do |
1 | daniel-mar | 62 | begin |
19 | daniel-mar | 63 | for x := Low(LevelArray[y].Fields) to High(LevelArray[y].Fields) do |
1 | daniel-mar | 64 | begin |
19 | daniel-mar | 65 | t := LevelArray[y].Fields[x].Typ; |
66 | indent := LevelArray[y].Indent; |
||
1 | daniel-mar | 67 | |
68 | case t of |
||
11 | daniel-mar | 69 | ftFullSpace: Image.Canvas.Brush.Color := BackgroundColor; |
19 | daniel-mar | 70 | ftEmpty: Image.Canvas.Brush.Color := clWhite; |
71 | ftGreen: Image.Canvas.Brush.Color := clLime; |
||
72 | ftYellow: Image.Canvas.Brush.Color := clYellow; |
||
73 | ftRed: Image.Canvas.Brush.Color := clRed; |
||
1 | daniel-mar | 74 | end; |
75 | |||
19 | daniel-mar | 76 | if LevelArray[y].Fields[x].Goal then |
1 | daniel-mar | 77 | Image.Canvas.Pen.Color := clBlack |
78 | else |
||
79 | Image.Canvas.Pen.Color := BackgroundColor; |
||
80 | |||
19 | daniel-mar | 81 | Image.Canvas.Rectangle(x*PREVIEW_BLOCK_SIZE + indent*PREVIEW_TAB_SIZE, |
11 | daniel-mar | 82 | y*PREVIEW_BLOCK_SIZE, |
19 | daniel-mar | 83 | x*PREVIEW_BLOCK_SIZE + indent*PREVIEW_TAB_SIZE + PREVIEW_BLOCK_SIZE, |
84 | y*PREVIEW_BLOCK_SIZE + PREVIEW_BLOCK_SIZE); |
||
1 | daniel-mar | 85 | end; |
86 | end; |
||
87 | end; |
||
88 | |||
11 | daniel-mar | 89 | function FieldTypeWorth(t: TFieldType): integer; |
1 | daniel-mar | 90 | begin |
11 | daniel-mar | 91 | if t = ftGreen then result := 10 |
92 | else if t = ftYellow then result := 20 |
||
93 | else if t = ftRed then result := 30 |
||
94 | else result := 0; |
||
1 | daniel-mar | 95 | end; |
96 | |||
11 | daniel-mar | 97 | { TLevel } |
98 | |||
99 | const NUM_HEADERS = 2; |
||
100 | |||
101 | constructor TLevel.Create(ABoardFile: string); |
||
1 | daniel-mar | 102 | begin |
11 | daniel-mar | 103 | inherited Create; |
104 | FStringList := TStringList.Create; |
||
105 | Load(ABoardFile); |
||
1 | daniel-mar | 106 | end; |
107 | |||
11 | daniel-mar | 108 | destructor TLevel.Destroy; |
1 | daniel-mar | 109 | begin |
11 | daniel-mar | 110 | FreeAndNil(FStringList); |
111 | |||
112 | inherited; |
||
1 | daniel-mar | 113 | end; |
114 | |||
11 | daniel-mar | 115 | function TLevel.GetGameMode: TGameMode; |
116 | begin |
||
117 | if LowerCase(FStringList.Strings[1]) = 'mode: normal' then |
||
118 | result := gmNormal |
||
119 | else if LowerCase(FStringList.Strings[1]) = 'mode: diagonal' then |
||
120 | result := gmDiagonal |
||
121 | else |
||
122 | result := gmUndefined; |
||
123 | end; |
||
124 | |||
125 | procedure TLevel.Load(ABoardFile: string); |
||
1 | daniel-mar | 126 | var |
11 | daniel-mar | 127 | i: Integer; |
1 | daniel-mar | 128 | begin |
11 | daniel-mar | 129 | FStringList.Clear; |
130 | FStringList.LoadFromFile(ABoardFile); |
||
1 | daniel-mar | 131 | |
11 | daniel-mar | 132 | // Remove whitespaces and empty lines |
133 | for i := FStringList.Count-1 downto NUM_HEADERS do |
||
1 | daniel-mar | 134 | begin |
11 | daniel-mar | 135 | FStringList.Strings[i] := StringReplace(FStringList.Strings[i], ' ', '', [rfReplaceAll]); |
136 | if FStringList.Strings[i] = '' then FStringList.Delete(i); |
||
1 | daniel-mar | 137 | end; |
11 | daniel-mar | 138 | end; |
1 | daniel-mar | 139 | |
19 | daniel-mar | 140 | function DotsAtBeginning(s: string): integer; |
141 | var |
||
142 | i: integer; |
||
143 | begin |
||
144 | result := 0; |
||
145 | for i := 1 to Length(s) do |
||
146 | begin |
||
147 | if s[i] = '.' then |
||
148 | Inc(result) |
||
149 | else |
||
150 | Exit; |
||
151 | end; |
||
152 | end; |
||
153 | |||
154 | function DotsAtEnd(s: string): integer; |
||
155 | var |
||
156 | i: integer; |
||
157 | begin |
||
158 | result := 0; |
||
159 | for i := Length(s) downto 1 do |
||
160 | begin |
||
161 | if s[i] = '.' then |
||
162 | Inc(result) |
||
163 | else |
||
164 | Exit; |
||
165 | end; |
||
166 | end; |
||
167 | |||
11 | daniel-mar | 168 | function TLevel.LevelStringToLevelArray(ShowErrors: boolean): TLevelArray; |
169 | var |
||
170 | i: integer; |
||
171 | t: TFieldType; |
||
172 | err: TLevelError; |
||
173 | y: Integer; |
||
174 | x: Integer; |
||
175 | Line: string; |
||
176 | lch, uch: char; |
||
177 | ch: char; |
||
178 | begin |
||
179 | // Zuerst nach Fehlern suchen |
||
180 | err := CheckLevelIntegrity(ShowErrors); |
||
181 | if err <> leNone then exit; |
||
182 | |||
183 | // Nun Matrix aufbauen |
||
184 | SetLength(result, 0); |
||
185 | for i := NUM_HEADERS to FStringList.Count-1 do |
||
1 | daniel-mar | 186 | begin |
11 | daniel-mar | 187 | y := i - NUM_HEADERS; |
1 | daniel-mar | 188 | |
11 | daniel-mar | 189 | SetLength(result, Length(result)+1); // add line to matrix |
1 | daniel-mar | 190 | |
11 | daniel-mar | 191 | Line := FStringList.Strings[i]; |
19 | daniel-mar | 192 | result[y].Indent := DotsAtBeginning(Line) - DotsAtEnd(Line); |
193 | Line := StringReplace(Line, '.', '', [rfReplaceAll]); |
||
194 | SetLength(result[y].Fields, Length(Line)); |
||
1 | daniel-mar | 195 | |
19 | daniel-mar | 196 | for x := 0 to Length(Line)-1 do |
11 | daniel-mar | 197 | begin |
19 | daniel-mar | 198 | ch := Line[x+1]; |
11 | daniel-mar | 199 | lch := LowerCase(ch)[1]; |
200 | uch := UpperCase(ch)[1]; |
||
19 | daniel-mar | 201 | |
202 | t := ftUndefined; |
||
11 | daniel-mar | 203 | case lch of |
204 | '*': t := ftFullSpace; |
||
205 | 'e': t := ftEmpty; |
||
206 | 'r': t := ftRed; |
||
207 | 'y': t := ftYellow; |
||
208 | 'g': t := ftGreen; |
||
209 | end; |
||
1 | daniel-mar | 210 | |
19 | daniel-mar | 211 | result[y].Fields[x].Typ := t; |
212 | result[y].Fields[x].Goal := (ch = uch) and (ch <> lch); |
||
11 | daniel-mar | 213 | end; |
214 | end; |
||
215 | end; |
||
216 | |||
217 | function TLevel.CheckLevelIntegrity(ShowErrors: boolean): TLevelError; |
||
218 | resourcestring |
||
219 | 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 *.'; |
||
220 | LNG_LVL_UNSUPPORTED_VERSION = 'Level format invalid: Version not supported.'; |
||
221 | LNG_LVL_UNSUPPORTED_MODE = 'Level format invalid: Mode not supported.'; |
||
222 | LNG_LVL_EMPTY_BOARD = 'Level invalid: Board is empty.'; |
||
223 | LNG_LVL_INVALID_LENGTH = 'Level invalid: Lines don''t have an equal amount of elements.'; |
||
224 | begin |
||
225 | result := CheckLevelIntegrity; |
||
226 | if ShowErrors then |
||
1 | daniel-mar | 227 | begin |
11 | daniel-mar | 228 | case result of |
229 | leNone: ; |
||
230 | leInvalidElement: MessageDlg(LNG_LVL_INVALID_ELEMENT, mtError, [mbOk], 0); |
||
231 | leUnsupportedVersion: MessageDlg(LNG_LVL_UNSUPPORTED_VERSION, mtError, [mbOk], 0); |
||
232 | leUnsupportedMode: MessageDlg(LNG_LVL_UNSUPPORTED_MODE, mtError, [mbOk], 0); |
||
233 | leEmptyBoard: MessageDlg(LNG_LVL_EMPTY_BOARD, mtError, [mbOk], 0); |
||
234 | leRowInvalidLength: MessageDlg(LNG_LVL_INVALID_LENGTH, mtError, [mbOk], 0); |
||
1 | daniel-mar | 235 | end; |
236 | end; |
||
11 | daniel-mar | 237 | end; |
1 | daniel-mar | 238 | |
11 | daniel-mar | 239 | function TLevel.CheckLevelIntegrity: TLevelError; |
240 | var |
||
19 | daniel-mar | 241 | tmp: string; |
11 | daniel-mar | 242 | i: Integer; |
243 | Line: string; |
||
19 | daniel-mar | 244 | firstLine: string; |
245 | thisLine: string; |
||
11 | daniel-mar | 246 | begin |
247 | result := leNone; |
||
1 | daniel-mar | 248 | |
11 | daniel-mar | 249 | // Check 1: Ist der Header OK? |
1 | daniel-mar | 250 | |
11 | daniel-mar | 251 | if LowerCase(FStringList.Strings[0]) <> 'version 2' then |
1 | daniel-mar | 252 | begin |
11 | daniel-mar | 253 | result := leUnsupportedVersion; |
254 | exit; |
||
1 | daniel-mar | 255 | end; |
256 | |||
11 | daniel-mar | 257 | if ((LowerCase(FStringList.Strings[1]) <> 'mode: normal') and (LowerCase(FStringList.Strings[1]) <> 'mode: diagonal')) then |
1 | daniel-mar | 258 | begin |
11 | daniel-mar | 259 | result := leUnsupportedMode; |
260 | exit; |
||
1 | daniel-mar | 261 | end; |
262 | |||
11 | daniel-mar | 263 | // Check 2: Ist das Brett leer? |
1 | daniel-mar | 264 | |
19 | daniel-mar | 265 | tmp := ''; |
266 | for i := NUM_HEADERS to FStringList.Count-1 do tmp := tmp + FStringList.Strings[i]; |
||
267 | if Trim(StringReplace(tmp, '.', '', [rfReplaceAll])) = '' then |
||
1 | daniel-mar | 268 | begin |
11 | daniel-mar | 269 | result := leEmptyBoard; |
270 | exit; |
||
1 | daniel-mar | 271 | end; |
272 | |||
11 | daniel-mar | 273 | // Check 3: Geht das Level nicht in einem Quadrat oder Rechteck auf? |
1 | daniel-mar | 274 | |
19 | daniel-mar | 275 | firstLine := StringReplace(FStringList.Strings[NUM_HEADERS], '.', '', [rfReplaceAll]); |
11 | daniel-mar | 276 | for i := NUM_HEADERS to FStringList.Count-1 do |
1 | daniel-mar | 277 | begin |
19 | daniel-mar | 278 | thisLine := StringReplace(FStringList.Strings[i], '.', '', [rfReplaceAll]); |
279 | if Length(thisLine) <> Length(firstLine) then |
||
11 | daniel-mar | 280 | begin |
281 | result := leRowInvalidLength; // at row y-NUM_HEADERS |
||
282 | exit; |
||
283 | end; |
||
1 | daniel-mar | 284 | end; |
285 | |||
11 | daniel-mar | 286 | // Check 4: Gibt es ungültige Elemente in den Zeilen? |
1 | daniel-mar | 287 | |
11 | daniel-mar | 288 | for i := NUM_HEADERS to FStringList.Count-1 do |
289 | begin |
||
290 | Line := FStringList.Strings[i]; |
||
1 | daniel-mar | 291 | |
11 | daniel-mar | 292 | Line := StringReplace(Line, '.', '', [rfReplaceAll]); |
293 | Line := StringReplace(Line, '*', '', [rfReplaceAll]); |
||
294 | Line := StringReplace(Line, 'r', '', [rfReplaceAll, rfIgnoreCase]); |
||
295 | Line := StringReplace(Line, 'y', '', [rfReplaceAll, rfIgnoreCase]); |
||
296 | Line := StringReplace(Line, 'g', '', [rfReplaceAll, rfIgnoreCase]); |
||
297 | Line := StringReplace(Line, 'e', '', [rfReplaceAll, rfIgnoreCase]); |
||
1 | daniel-mar | 298 | |
11 | daniel-mar | 299 | if Length(Line) > 0 then |
1 | daniel-mar | 300 | begin |
11 | daniel-mar | 301 | result := leInvalidElement; // at row y-NUM_HEADERS |
302 | Exit; |
||
1 | daniel-mar | 303 | end; |
304 | end; |
||
305 | |||
11 | daniel-mar | 306 | // Check 5: Kann im Level gesprungen werden |
307 | |||
308 | { Wird hier nicht abgeprüft, da dafür zuerst der PlayGround gebaut sein muss. |
||
309 | Es ist außerdem eher ein logischer Fehler, kein Fehler in der Levelstruktur! } |
||
8 | daniel-mar | 310 | end; |
311 | |||
1 | daniel-mar | 312 | end. |