I am a new data science student and I'm trying to understand design patterns and how to program a C# class the "finest" way possible.
I'm trying to build a class using the "singleton design pattern" while following the SOLID principles. I feel the class follows said principles and pattern quite well, but is there something that could be improved? Having a more experienced programmer's feedback and maybe another way of structuring the code would come along way for me!
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using DIKUArcade.Entities;
using DIKUArcade.Graphics;
using DIKUArcade.Math;
// Singleton Design Pattern with the SOLID principles in mind
namespace SpaceTaxi_2 {
public class LevelParser {
private LevelParser() {}
private static class LevelParserHolder {
public static readonly LevelParser INSTANCE = new LevelParser();
}
public static LevelParser GetInstance() {
return LevelParserHolder.INSTANCE;
}
public Dictionary<char, string> Platforms = new Dictionary<char, string>();
public Dictionary<char, string> Information = new Dictionary<char, string>();
public Dictionary<char, string> Exits = new Dictionary<char, string>();
public List<Picture> InformationEntity = new List<Picture>();
public List<Picture> PlatformEntity = new List<Picture>();
public List<Picture> ExitEntity = new List<Picture>();
public void LoadParser(string directory, string fileName) {
var content = GetContent(directory, fileName);
Platforms = GetPlatforms(content);
Information = GetTranslator(content);
Exits = GetExits(content);
var map = GetGameMap(content);
AddEntities(GetGameMap(content));
}
/// <summary>
/// Checks whether a file exists or not
/// </summary>
private void ValidatePath(string file) {
if (!File.Exists(file)) {
throw new FileNotFoundException($"Error: The path to \"{file}\" does not exist.");
}
}
/// <summary>
/// Finds full path to directory (e.g. directoryName: "Levels" or "Assets")
/// Starts from /bin/Debug folder, then goes to parent /bin, and so on.
/// Casts an exception if we iterated down to the root of the tree.
/// </summary>
private string GetPath(string directoryName) {
DirectoryInfo dir = new DirectoryInfo(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location));
while (dir.Name != directoryName) {
if (dir.FullName == dir.Root.Name) {
throw new FileNotFoundException($"Error: Directory \"{directoryName}\" does not exist.");
} else {
foreach (var i in dir.GetDirectories()) {
if (i.Name == directoryName) {
return i.FullName;
}
}
dir = dir.Parent;
}
}
return dir.FullName;
}
/// <summary>
/// Collects a string[] containing all strings in the file
/// </summary>
private string[] GetContent(string directoryName, string fileName) {
var dir = GetPath(directoryName);
string path = Path.Combine(dir, fileName);
ValidatePath(path);
return File.ReadAllLines(path);
}
/// <summary>
/// Extracts ASCII characters from the txt file and appends it
/// to an array. Later we will use this array to draw pictures.
/// </summary>
private List<char> GetGameMap(string[] txtFile) {
var charList = new List<char>();
for (int i = 0; i < 23; i++) {
foreach (var j in txtFile[i]) {
charList.Add(j);
}
}
return charList;
}
/// <summary>
/// Extracts the txt information about what each character represents.
/// Example from the-beach.txt: "A) aspargus-edge-left.png"
/// Here we split ')' to first get the character, A, and it's
/// corrospondent filename, 'aspargus-edge-left.png'.
/// </summary>
private Dictionary<char,string> GetTranslator(string[] txtFile) {
var dict = new Dictionary<char, string>();
foreach (var i in txtFile) {
if (i.Contains(")")) {
var tile = i.Split(')')[0].ToCharArray()[0];
var name = i.Split(')')[1].Trim();
dict.Add(tile, name);
}
}
return dict;
}
/// <summary>
/// Works the same way as GetTranslator, but finds only platforms
/// </summary>
private Dictionary<char,string> GetPlatforms(string[] txtFile) { // Read entire file
// Search through a .txt file, find the string "Platforms" and take it's following characters
// Example: "Platforms: J, i, r". We take J, i, r and put it in a dictionary with its correspondent filename.png
IEnumerable<string> findPlatforms = txtFile.Where(l => l.StartsWith("Platforms"));
var platforms = findPlatforms.First().Split(':')[1].Split(',');
// Add each char and filename to a dictionary
var dict = new Dictionary<char, string>();
foreach (var i in platforms) {
var line = txtFile.Where(l => l.StartsWith(i.Trim() + ") "));
var tile = line.First().Split(' ', ')')[0].ToCharArray()[0];
var filename = line.First().Split(' ')[1];
dict.Add(tile, filename);
}
return dict;
}
/// <summary>
/// Works the same way as GetTranslator, but finds only exits
/// </summary>
private Dictionary<char, string> GetExits(string[] txtFile) {
var dict = new Dictionary<char,string>();
foreach (var i in txtFile) {
if (i.Contains('^')) {
dict.Add('^', "aspargus-passage.png");
return dict;
}
}
return dict;
}
/// <summary>
/// We iterate from top-left (0,1) of the program to bottom-right (1,0)
/// </summary>
private List<Entity> AddEntities(List<char> map) {
List<Entity> entityContainer = new List<Entity>();
float tmpX = Constants.X_MIN;
float tmpY = Constants.Y_MAX;
int index = 0;
// Going from top (y 1.0) to bottom (y 0.0)
while (tmpY > Constants.Y_MIN)
{
// Going from left (x 0.0) to right (x 1.0)
while (tmpX < Constants.X_MAX)
{
// There can be empty strings in our list of strings.
if (map[index].ToString() == " ")
{
index += 1;
}
else
{
if (Information.ContainsKey(map[index])) {
var shape = new StationaryShape(new Vec2F(tmpX, tmpY), new Vec2F(Constants.WIDTH, Constants.HEIGHT));
var file = Path.Combine(GetPath("Assets"), "Images", Information[map[index]]);
Console.WriteLine(file);
ValidatePath(file);
InformationEntity.Add(new Picture(shape, new Image(file)));
}
if (Platforms.ContainsKey(map[index])) {
var shape = new StationaryShape(new Vec2F(tmpX, tmpY), new Vec2F(Constants.WIDTH, Constants.HEIGHT));
var file = Path.Combine(GetPath("Assets"), "Images", Platforms[map[index]]);
ValidatePath(file);
PlatformEntity.Add(new Picture(shape, new Image(file)));
}
if (Exits.ContainsKey(map[index])) {
var shape = new StationaryShape(new Vec2F(tmpX, tmpY), new Vec2F(Constants.WIDTH, Constants.HEIGHT));
var file = Path.Combine(GetPath("Assets"), "Images", Exits[map[index]]);
ValidatePath(file);
ExitEntity.Add(new Picture(shape, new Image(file)));
}
index += 1;
}
tmpX += Constants.WIDTH;
}
tmpX = 0;
tmpY -= Constants.HEIGHT;
}
return entityContainer;
}
}
}
The class is a parser and should basically read a .txt file and translate it into lists with entities containing images.
For example, with "LoadParser" I was thinking of giving an opportunity for someone calling the class to easily "activate" the other methods, but not quite sure if this approach is okay.
Aucun commentaire:
Enregistrer un commentaire