X-Git-Url: https://git.dogcows.com/gitweb?p=chaz%2Fcarfire;a=blobdiff_plain;f=CarFire%2FCarFire%2FCarFire%2FMapReader.cs;h=347f6bd87309164dfb440d3500c07f4cea824121;hp=35089ba6531d7345f15e0a164790f01f81c3d322;hb=e8ee0aa62a7e8b5dffa9e02c00c3e353a9e93b4c;hpb=c5daf1d9adca0c3a826dfa2ac7b6d4f8a64c84a3 diff --git a/CarFire/CarFire/CarFire/MapReader.cs b/CarFire/CarFire/CarFire/MapReader.cs index 35089ba..347f6bd 100644 --- a/CarFire/CarFire/CarFire/MapReader.cs +++ b/CarFire/CarFire/CarFire/MapReader.cs @@ -2,10 +2,10 @@ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; -using System.Text.RegularExpressions; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; +using System.Reflection; namespace CarFire { @@ -15,272 +15,411 @@ namespace CarFire /// public class MapReader : ContentTypeReader { - public class ParserException : System.ApplicationException + #region Protected Methods + + protected override Map Read(ContentReader input, Map existingInstance) { - public ParserException() - { - } + mImpl = new Impl(input); + return mImpl.GetMap(); + } - public ParserException(string message) : - base(message) - { - } + #endregion + + + #region Private Types + + /// + /// This private class wraps around ContentReader to make it more + /// convenient to use it as an input stream reader. + /// + class LineReader + { + ContentReader mInput; + int mLineNumber = 0; + int mExpectedNumberOfLines; - public ParserException(string message, System.Exception inner) : - base(message, inner) + public LineReader(ContentReader input) { + mInput = input; + mExpectedNumberOfLines = mInput.ReadInt32(); } - protected ParserException(SerializationInfo info, StreamingContext context) : - base(info, context) + public string ReadLine() { + mLineNumber++; + return mInput.ReadString(); } - } - ContentReader mInput; - int mLineNumber = 0; - int mExpectedNumberOfLines; + public int LineNumber { get { return mLineNumber; } } - Map.Data mData; - - - protected override Map Read(ContentReader input, Map existingInstance) - { - mInput = input; - mExpectedNumberOfLines = mInput.ReadInt32(); - - ReadData(); - - return new Map(mData); + public bool End { get { return mLineNumber >= mExpectedNumberOfLines; } } } - #region Private Reading Methods - - string ReadLine() + /// + /// This class is the actual implementation. The implementation is wrapped + /// in a subclass because the invoker seems to only be able to invoke public + /// methods, and this needs to invoke methods that shouldn't be public. + /// + class Impl { - return mInput.ReadString(); - } + public Impl(ContentReader input) + { + mInput = new LineReader(input); + ReadSectionHeaders(); + PostProcess(); + } + + public Map GetMap() + { + return new Map(mMetadata, mGrid, mDefaultTile, mEntities, mPlayerPositions); + } - void ReadData() - { - mData = new Map.Data(); - while (mLineNumber < mExpectedNumberOfLines) + public void ReadSectionHeaders() { - string line = ReadLine(); - mLineNumber++; + mMetadata = new Map.Metadata(); - while (line != null) + while (!mInput.End) { - if (!IsLineSignificant(line)) break; + string line = mInput.ReadLine(); - string section = Parse.IniSectionHeader(line); - if (section != null) + while (line != null) { - if (section == "metadata") + if (!IsLineSignificant(line)) break; + + string section = Parse.IniSectionHeader(line); + if (section != null) { - line = ReadMetadataSection(); + if (section == "metadata") + { + line = ReadMetadataSection(); + } + else if (section == "maptable") + { + line = ReadMapTableSection(); + } + else if (section.Length == 1 && IsValidEntityIdentifier(section[0])) + { + line = ReadEntitySection(section[0]); + } + else + { + throw new Exception("Unexpected section on line " + mInput.LineNumber + ": " + section); + } } - else if (section == "maptable") + else { - line = ReadMapTableSection(); + throw new Exception("Unexpected text on line " + mInput.LineNumber + ": " + line); } - else if (section.Length == 1) + } + } + } + + string ReadMetadataSection() + { + while (!mInput.End) + { + string line = mInput.ReadLine(); + if (!IsLineSignificant(line)) continue; + + string[] pair = Parse.KeyValuePair(line); + if (pair != null) + { + try { - line = ReadEntitySection(section[0]); + string methodName = "set_" + pair[0].ToLowerInvariant(); + object[] args = new object[1]; + args[0] = pair[1]; + GetType().InvokeMember(methodName, BindingFlags.InvokeMethod, null, this, args); } - else +#pragma warning disable 0168 + catch (System.MissingMethodException ex) +#pragma warning restore 0168 { - ThrowException("Unknown section", section); + throw new Exception("Unexpected key on line " + mInput.LineNumber + ": " + pair[0]); } } else { - ThrowException("Unexpected text", line); + return line; } } + + return null; } - } - string ReadMetadataSection() - { - while (mLineNumber < mExpectedNumberOfLines) + string ReadMapTableSection() { - string line = ReadLine(); - mLineNumber++; + if (mMetadata == null || mMetadata.GridWidth == 0 || mMetadata.GridHeight == 0) + { + throw new Exception("Unexpected section on line " + mInput.LineNumber + + ": You must define the map dimensions before this section."); + } - if (!IsLineSignificant(line)) continue; + mGrid = new char[mMetadata.GridWidth, mMetadata.GridHeight]; - string[] pair = Parse.KeyValuePair(line); - if (pair != null) + int y; + for (y = 0; y < mMetadata.GridHeight && !mInput.End; y++) { - if (pair[0] == "type") + string line = mInput.ReadLine(); + + if (line.Length < mMetadata.GridWidth) { - object type = Parse.Constant(pair[1]); - if (type != null) - { - mData.Type = (Map.Type)type; - } - else - { - ThrowException("Invalid type", pair[1]); - } + throw new Exception("Unexpected EOL on line " + mInput.LineNumber + + ": The number of characters should match the width dimension (" + mMetadata.GridWidth + ")."); } - else if (pair[0] == "dimensions") + + for (int x = 0; x < mMetadata.GridWidth; x++) { - Point? dimensions = Parse.Coordinates(pair[1]); - if (dimensions != null) - { - mData.Dimensions = dimensions.Value; - if (mData.Dimensions.X <= 0 || mData.Dimensions.Y <= 0) - { - ThrowException("Invalid dimensions", pair[1]); - } - } - else - { - ThrowException("Invalid value", pair[1]); - } + mGrid[x, y] = line[x]; } - else if (pair[0] == "tileset") + } + + if (y < mMetadata.GridHeight) + { + throw new Exception("Unexpected EOF on line " + mInput.LineNumber + + ": The number of lines in this section should match the height dimension (" + mMetadata.GridHeight + ")."); + } + + return null; + } + + string ReadEntitySection(char identifier) + { + Dictionary pairs = new Dictionary(); + mEntitySections[identifier] = pairs; + + while (!mInput.End) + { + string line = mInput.ReadLine(); + + string[] pair = Parse.KeyValuePair(line); + if (pair != null) { - string tileset = Parse.String(pair[1]); - if (tileset != null) - { - mData.Tileset = tileset; - } - else - { - ThrowException("Invalid tileset", pair[1]); - } + pairs[pair[0]] = pair[1]; } - else if (pair[0] == "numplayers") + else { - int[] numPlayers = Parse.Range(pair[1]); - if (numPlayers != null) - { - mData.MinNumPlayers = numPlayers[0]; - mData.MaxNumPlayers = numPlayers[1]; - if (mData.MinNumPlayers <= 0 || mData.MaxNumPlayers <= 0 || - mData.MinNumPlayers > mData.MaxNumPlayers) - { - ThrowException("Invalid range", pair[1]); - } - } - else - { - ThrowException("Invalid value", pair[1]); - } + return line; } - else if (pair[0] == "author") + } + + return null; + } + + + void PostProcess() + { + if (mMetadata == null || mGrid == null) + { + throw new Exception("Missing a required section. Make sure the metadata and grid are there."); + } + + mEntities = new List(); + mPlayerPositions = new Point[mMetadata.NumPlayers.Max() + 1]; + + // create entities defined completely + foreach (char identifier in mEntitySections.Keys) + { + Dictionary pairs = mEntitySections[identifier]; + if (pairs.ContainsKey("create")) { - string author = Parse.String(pair[1]); - if (author != null) + string[] list = Parse.List(pairs["create"]); + foreach (string positionString in list) { - mData.Author = author; - } - else - { - ThrowException("Invalid value", pair[1]); + Point? position = Parse.Coordinates(positionString); + if (position != null) + { + Map.RawEntity createEntity = new Map.RawEntity(); + createEntity.Id = identifier; + createEntity.Position = position.Value; + createEntity.Attributes = pairs; + mEntities.Add(createEntity); + } + else + { + throw new Exception("Unexpected value of key `create' defined for entity " + identifier + "."); + } } + pairs.Remove("create"); } - else if (pair[0] == "levelname") + } + + // create entities with positions defined on the grid + // and get player starting positions + for (int x = 0; x < mMetadata.GridWidth; x++) + { + for (int y = 0; y < mMetadata.GridHeight; y++) { - string level = Parse.String(pair[1]); - if (level != null) + char identifier = mGrid[x, y]; + if (IsValidEntityIdentifier(identifier)) { - mData.Name = level; + if (mEntitySections.ContainsKey(identifier)) + { + Map.RawEntity createEntity = new Map.RawEntity(); + createEntity.Id = identifier; + createEntity.Position = new Point(x, y); + createEntity.Attributes = mEntitySections[identifier]; + mEntities.Add(createEntity); + } + else + { + throw new Exception("Unexpected entity (" + identifier + + ") placed on the grid at [" + x + "," + y + "] but not defined."); + } + mGrid[x, y] = mDefaultTile; } - else + else if ('1' <= identifier && identifier <= '9') { - ThrowException("Invalid value", pair[1]); + int playerNum = identifier - 48; + if (playerNum < mPlayerPositions.Count()) + { + mPlayerPositions[playerNum] = new Point(x, y); + } + mGrid[x, y] = mDefaultTile; } } - else - { - Console.WriteLine("Unimplemented key: " + pair[0]); - } } - else + + // check if all needed player positions are defined + for (int i = 1; i < mPlayerPositions.Count(); i++) { - return line; + if (mPlayerPositions[i] == default(Point)) + { + throw new Exception("Not enough player positions were defined on the grid; " + + "you are missing a spot for player " + i + "."); + } } } - return null; - } - string ReadMapTableSection() - { - if (mData.Dimensions.X == 0 || mData.Dimensions.Y == 0) + bool IsLineSignificant(string line) { - ThrowException("Unexpected section", "You must define the map dimensions before this section."); + if (line.Trim().Length == 0 || Parse.IniComment(line) != null) return false; + return true; + } + + bool IsValidEntityIdentifier(char id) + { + if (('a' <= id && id <= 'z') || ('A' <= id && id <= 'Z')) return true; + return false; } - mData.Grid = new char[mData.Dimensions.X, mData.Dimensions.Y]; - int y; - for (y = 0; y < mData.Dimensions.Y && mLineNumber < mExpectedNumberOfLines; y++) + public void set_author(string atom) { - string line = ReadLine(); - mLineNumber++; + string value = Parse.String(atom); + if (value != null) mMetadata.Author = value; + else throw new Exception("Unexpected value on line " + mInput.LineNumber + ": " + atom); + } - if (line.Length < mData.Dimensions.X) + public void set_levelname(string atom) + { + string value = Parse.String(atom); + if (value != null) mMetadata.Name = value; + else throw new Exception("Unexpected value on line " + mInput.LineNumber + ": " + atom); + } + + public void set_next(string atom) + { + string value = Parse.String(atom); + if (value != null) mMetadata.Next = value; + else throw new Exception("Unexpected value on line " + mInput.LineNumber + ": " + atom); + } + + public void set_type(string atom) + { + Map.Mode? value = Parse.Constant(atom); + if (value != null) mMetadata.Type = value.Value; + else throw new Exception("Unexpected type on line " + mInput.LineNumber + ": " + atom); + } + + public void set_dimensions(string atom) + { + Point? dimensions = Parse.Coordinates(atom); + if (dimensions != null) { - ThrowException("Not enough characters", "Expecting " + mData.Dimensions.X + " characters."); + mMetadata.GridWidth = dimensions.Value.X; + mMetadata.GridHeight = dimensions.Value.Y; + if (mMetadata.GridWidth <= 0 || mMetadata.GridHeight <= 0) + { + throw new Exception("Invalid dimensions on line " + mInput.LineNumber + ": " + atom); + } } - - for (int x = 0; x < mData.Dimensions.X; x++) + else { - mData.Grid[x, y] = line[x]; + throw new Exception("Unexpected value on line " + mInput.LineNumber + ": " + atom); } } - if (y < mData.Dimensions.Y) + public void set_tileset(string atom) { - ThrowException("Unexpected ", ""); + string value = Parse.String(atom); + if (value != null) mMetadata.Tileset = value; + else throw new Exception("Unexpected tileset on line " + mInput.LineNumber + ": " + atom); } - return null; - } - - string ReadEntitySection(char entity) - { - while (mLineNumber < mExpectedNumberOfLines) + public void set_defaulttile(string atom) { - string line = ReadLine(); - mLineNumber++; + char? value = Parse.Char(atom); + if (value != null) mDefaultTile = value.Value; + else throw new Exception("Unexpected tile value on line " + mInput.LineNumber + ": " + atom); + } - string[] pair = Parse.KeyValuePair(line); - if (pair != null) + public void set_numplayers(string atom) + { + string[] list = Parse.List(atom); + if (list != null) { - // TODO + foreach (string item in list) + { + int[] range = Parse.Range(item); + if (range != null) + { + for (int i = range[0]; i <= range[1]; i++) + { + mMetadata.NumPlayers.Add(i); + } + continue; + } + int? integer = Parse.Integer(item); + if (integer != null) + { + mMetadata.NumPlayers.Add(integer.Value); + continue; + } + + throw new Exception("Unexpected atom on line " + mInput.LineNumber + ": " + item); + } + if (mMetadata.NumPlayers.Count == 0) + { + throw new Exception("No numbers given on line " + mInput.LineNumber + ": " + atom); + } } else { - return line; + throw new Exception("Unexpected value on line " + mInput.LineNumber + ": " + atom); } } - return null; + + Map.Metadata mMetadata; + char[,] mGrid; + List mEntities; + Point[] mPlayerPositions; + char mDefaultTile = ' '; + + Dictionary> mEntitySections = new Dictionary>(); + LineReader mInput; } #endregion - #region Private Methods + #region Private Variables - bool IsLineSignificant(string line) - { - if (line.Length == 0 || Regex.IsMatch(line, @"^;|^\s*$")) return false; - return true; - } - - void ThrowException(string problem, string text) - { - throw new ParserException(problem + " on line " + mLineNumber + ": " + text); - } + Impl mImpl; #endregion }