]> Dogcows Code - chaz/carfire/blob - CarFire/CarFire/CarFire/MapReader.cs
Slightly improved commenting.
[chaz/carfire] / CarFire / CarFire / CarFire / MapReader.cs
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Runtime.Serialization;
5 using Microsoft.Xna.Framework;
6 using Microsoft.Xna.Framework.Content;
7 using Microsoft.Xna.Framework.Graphics;
8 using System.Reflection;
9
10 namespace CarFire
11 {
12 /// <summary>
13 /// This class will be instantiated by the XNA Framework Content
14 /// Pipeline to read cfmap files from binary .xnb format.
15 /// </summary>
16 public class MapReader : ContentTypeReader<Map>
17 {
18 #region Public Exceptions
19
20 /// <summary>
21 /// This exception is thrown during the loading of a map if any
22 /// part of the map file is inconsistent with the expected format
23 /// and order.
24 /// </summary>
25 public class ParserException : System.ApplicationException
26 {
27 public ParserException() {}
28
29 public ParserException(string message) :
30 base(message) {}
31
32 public ParserException(string message, System.Exception inner) :
33 base(message, inner) {}
34
35 protected ParserException(SerializationInfo info, StreamingContext context) :
36 base(info, context) {}
37 }
38
39 #endregion
40
41
42 #region Protected Methods
43
44 protected override Map Read(ContentReader input, Map existingInstance)
45 {
46 mImpl = new Impl(input);
47 return mImpl.GetMap();
48 }
49
50 #endregion
51
52
53 #region Private Types
54
55 /// <summary>
56 /// This private class wraps around ContentReader to make it more
57 /// convenient to use it as an input stream reader.
58 /// </summary>
59 class LineReader
60 {
61 ContentReader mInput;
62 int mLineNumber = 0;
63 int mExpectedNumberOfLines;
64
65 public LineReader(ContentReader input)
66 {
67 mInput = input;
68 mExpectedNumberOfLines = mInput.ReadInt32();
69 }
70
71 public string ReadLine()
72 {
73 mLineNumber++;
74 return mInput.ReadString();
75 }
76
77 public int LineNumber { get { return mLineNumber; } }
78
79 public bool End { get { return mLineNumber >= mExpectedNumberOfLines; } }
80 }
81
82
83 /// <summary>
84 /// This class is the actual implementation. The implementation is wrapped
85 /// in a subclass because the invoker seems to only be able to invoke public
86 /// methods, and this needs to invoke methods that shouldn't be public.
87 /// </summary>
88 class Impl
89 {
90 public Impl(ContentReader input)
91 {
92 mInput = new LineReader(input);
93 ReadSectionHeaders();
94 PostProcess();
95 }
96
97 public Map GetMap()
98 {
99 return new Map(mMetadata, mGrid, mDefaultTile, mEntities, mPlayerPositions);
100 }
101
102
103 public void ReadSectionHeaders()
104 {
105 mMetadata = new Map.Metadata();
106
107 while (!mInput.End)
108 {
109 string line = mInput.ReadLine();
110
111 while (line != null)
112 {
113 if (!IsLineSignificant(line)) break;
114
115 string section = Parse.IniSectionHeader(line);
116 if (section != null)
117 {
118 if (section == "metadata")
119 {
120 line = ReadMetadataSection();
121 }
122 else if (section == "maptable")
123 {
124 line = ReadMapTableSection();
125 }
126 else if (section.Length == 1 && IsValidEntityIdentifier(section[0]))
127 {
128 line = ReadEntitySection(section[0]);
129 }
130 else
131 {
132 throw new ParserException("Unexpected section on line " + mInput.LineNumber + ": " + section);
133 }
134 }
135 else
136 {
137 throw new ParserException("Unexpected text on line " + mInput.LineNumber + ": " + line);
138 }
139 }
140 }
141 }
142
143 string ReadMetadataSection()
144 {
145 while (!mInput.End)
146 {
147 string line = mInput.ReadLine();
148 if (!IsLineSignificant(line)) continue;
149
150 string[] pair = Parse.KeyValuePair(line);
151 if (pair != null)
152 {
153 try
154 {
155 string methodName = "set_" + pair[0].ToLowerInvariant();
156 object[] args = new object[1];
157 args[0] = pair[1];
158 GetType().InvokeMember(methodName, BindingFlags.InvokeMethod, null, this, args);
159 }
160 #pragma warning disable 0168
161 catch (System.MissingMethodException ex)
162 #pragma warning restore 0168
163 {
164 throw new ParserException("Unexpected key on line " + mInput.LineNumber + ": " + pair[0]);
165 }
166 }
167 else
168 {
169 return line;
170 }
171 }
172
173 return null;
174 }
175
176 string ReadMapTableSection()
177 {
178 if (mMetadata == null || mMetadata.GridWidth == 0 || mMetadata.GridHeight == 0)
179 {
180 throw new ParserException("Unexpected section on line " + mInput.LineNumber +
181 ": You must define the map dimensions before this section.");
182 }
183
184 mGrid = new char[mMetadata.GridWidth, mMetadata.GridHeight];
185
186 int y;
187 for (y = 0; y < mMetadata.GridHeight && !mInput.End; y++)
188 {
189 string line = mInput.ReadLine();
190
191 if (line.Length < mMetadata.GridWidth)
192 {
193 throw new ParserException("Unexpected EOL on line " + mInput.LineNumber +
194 ": The number of characters should match the width dimension (" + mMetadata.GridWidth + ").");
195 }
196
197 for (int x = 0; x < mMetadata.GridWidth; x++)
198 {
199 mGrid[x, y] = line[x];
200 }
201 }
202
203 if (y < mMetadata.GridHeight)
204 {
205 throw new ParserException("Unexpected EOF on line " + mInput.LineNumber +
206 ": The number of lines in this section should match the height dimension (" + mMetadata.GridHeight + ").");
207 }
208
209 return null;
210 }
211
212 string ReadEntitySection(char identifier)
213 {
214 Dictionary<string, string> pairs = new Dictionary<string, string>();
215 mEntitySections[identifier] = pairs;
216
217 while (!mInput.End)
218 {
219 string line = mInput.ReadLine();
220
221 string[] pair = Parse.KeyValuePair(line);
222 if (pair != null)
223 {
224 pairs[pair[0]] = pair[1];
225 }
226 else
227 {
228 return line;
229 }
230 }
231
232 return null;
233 }
234
235
236 void PostProcess()
237 {
238 if (mMetadata == null || mGrid == null)
239 {
240 throw new ParserException("Missing a required section. Make sure the metadata and grid are there.");
241 }
242
243 mEntities = new List<Map.RawEntity>();
244 mPlayerPositions = new Point[mMetadata.NumPlayers.Max() + 1];
245
246 // create entities defined completely
247 foreach (char identifier in mEntitySections.Keys)
248 {
249 Dictionary<string, string> pairs = mEntitySections[identifier];
250 if (pairs.ContainsKey("create"))
251 {
252 string[] list = Parse.List(pairs["create"]);
253 foreach (string positionString in list)
254 {
255 Point? position = Parse.Coordinates(positionString);
256 if (position != null)
257 {
258 Map.RawEntity createEntity = new Map.RawEntity();
259 createEntity.Id = identifier;
260 createEntity.Position = position.Value;
261 createEntity.Attributes = pairs;
262 mEntities.Add(createEntity);
263 }
264 else
265 {
266 throw new ParserException("Unexpected value of key `create' defined for entity " + identifier + ".");
267 }
268 }
269 pairs.Remove("create");
270 }
271 }
272
273 // create entities with positions defined on the grid
274 // and get player starting positions
275 for (int x = 0; x < mMetadata.GridWidth; x++)
276 {
277 for (int y = 0; y < mMetadata.GridHeight; y++)
278 {
279 char identifier = mGrid[x, y];
280 if (IsValidEntityIdentifier(identifier))
281 {
282 if (mEntitySections.ContainsKey(identifier))
283 {
284 Map.RawEntity createEntity = new Map.RawEntity();
285 createEntity.Id = identifier;
286 createEntity.Position = new Point(x, y);
287 createEntity.Attributes = mEntitySections[identifier];
288 mEntities.Add(createEntity);
289 }
290 else
291 {
292 throw new ParserException("Unexpected entity (" + identifier +
293 ") placed on the grid at [" + x + "," + y + "] but not defined.");
294 }
295 mGrid[x, y] = mDefaultTile;
296 }
297 else if ('1' <= identifier && identifier <= '9')
298 {
299 int playerNum = identifier - 48;
300 if (playerNum < mPlayerPositions.Count())
301 {
302 mPlayerPositions[playerNum] = new Point(x, y);
303 }
304 mGrid[x, y] = mDefaultTile;
305 }
306 }
307 }
308
309 // check if all needed player positions are defined
310 for (int i = 1; i < mPlayerPositions.Count(); i++)
311 {
312 if (mPlayerPositions[i] == default(Point))
313 {
314 throw new ParserException("Not enough player positions were defined on the grid; " +
315 "you are missing a spot for player " + i + ".");
316 }
317 }
318 }
319
320
321 bool IsLineSignificant(string line)
322 {
323 if (line.Trim().Length == 0 || Parse.IniComment(line) != null) return false;
324 return true;
325 }
326
327 bool IsValidEntityIdentifier(char id)
328 {
329 if (('a' <= id && id <= 'z') || ('A' <= id && id <= 'Z')) return true;
330 return false;
331 }
332
333
334 public void set_author(string atom)
335 {
336 string value = Parse.String(atom);
337 if (value != null) mMetadata.Author = value;
338 else throw new ParserException("Unexpected value on line " + mInput.LineNumber + ": " + atom);
339 }
340
341 public void set_levelname(string atom)
342 {
343 string value = Parse.String(atom);
344 if (value != null) mMetadata.Name = value;
345 else throw new ParserException("Unexpected value on line " + mInput.LineNumber + ": " + atom);
346 }
347
348 public void set_type(string atom)
349 {
350 Map.Mode value = Parse.Constant<Map.Mode>(atom);
351 if (value != default(Map.Mode)) mMetadata.Type = value;
352 else throw new ParserException("Unexpected type on line " + mInput.LineNumber + ": " + atom);
353 }
354
355 public void set_dimensions(string atom)
356 {
357 Point? dimensions = Parse.Coordinates(atom);
358 if (dimensions != null)
359 {
360 mMetadata.GridWidth = dimensions.Value.X;
361 mMetadata.GridHeight = dimensions.Value.Y;
362 if (mMetadata.GridWidth <= 0 || mMetadata.GridHeight <= 0)
363 {
364 throw new ParserException("Invalid dimensions on line " + mInput.LineNumber + ": " + atom);
365 }
366 }
367 else
368 {
369 throw new ParserException("Unexpected value on line " + mInput.LineNumber + ": " + atom);
370 }
371 }
372
373 public void set_tileset(string atom)
374 {
375 string value = Parse.String(atom);
376 if (value != null) mMetadata.Tileset = value;
377 else throw new ParserException("Unexpected tileset on line " + mInput.LineNumber + ": " + atom);
378 }
379
380 public void set_defaulttile(string atom)
381 {
382 char? value = Parse.Char(atom);
383 if (value != null) mDefaultTile = value.Value;
384 else throw new ParserException("Unexpected tile value on line " + mInput.LineNumber + ": " + atom);
385 }
386
387 public void set_numplayers(string atom)
388 {
389 string[] list = Parse.List(atom);
390 if (list != null)
391 {
392 foreach (string item in list)
393 {
394 int[] range = Parse.Range(item);
395 if (range != null)
396 {
397 for (int i = range[0]; i <= range[1]; i++)
398 {
399 mMetadata.NumPlayers.Add(i);
400 }
401 continue;
402 }
403 int? integer = Parse.Integer(item);
404 if (integer != null)
405 {
406 mMetadata.NumPlayers.Add(integer.Value);
407 continue;
408 }
409
410 throw new ParserException("Unexpected atom on line " + mInput.LineNumber + ": " + item);
411 }
412 if (mMetadata.NumPlayers.Count == 0)
413 {
414 throw new ParserException("No numbers given on line " + mInput.LineNumber + ": " + atom);
415 }
416 }
417 else
418 {
419 throw new ParserException("Unexpected value on line " + mInput.LineNumber + ": " + atom);
420 }
421 }
422
423
424 Map.Metadata mMetadata;
425 char[,] mGrid;
426 List<Map.RawEntity> mEntities;
427 Point[] mPlayerPositions;
428 char mDefaultTile = ' ';
429
430 Dictionary<char, Dictionary<string, string>> mEntitySections = new Dictionary<char, Dictionary<string, string>>();
431 LineReader mInput;
432 }
433
434 #endregion
435
436
437 #region Private Variables
438
439 Impl mImpl;
440
441 #endregion
442 }
443 }
This page took 0.057488 seconds and 4 git commands to generate.