How to avoid a switch block with 300 cases? - c#

I am trying to add 300 Challenges into my program, but only display them if the CompletionValue.IsChecked = false;
If you were creating this program. How would you store the Challenges?
I am using a switch but having 300 cases is overkill, is there a better way?
Any recommendation on to improve the code is well appreciated.
I am somewhat new to this.
Random rand = new Random();
// Constructor
public MainPage()
{
InitializeComponent();
AnswerValue.Visibility = Visibility.Collapsed;
Load();
}
private void Load()
{
int random = rand.Next(1, 4);
switch (random)
{
case 1:
Challenge1();
break;
case 2:
Challenge2();
break;
case 3:
Challenge3();
break;
}
}
private void Challenge1()
{
DifficultyValue.Text = "20%";
CompletionValue.IsChecked = false;
TitleValue.Text = "Chicken or Egg?";
QuestionValue.Text = "Can you answer the ancient question: Which came first the chicken of the egg?";
bmp.UriSource = new Uri("Images/Challenge1.png", UriKind.Relative);
ImageValue.Source = bmp;
ImageValue.Visibility = Visibility.Visible;
ResourceValue.Text = "Resource: Brain Games";
AnswerValue.Text = "The Egg. According to paleontologists, reptiles and dinosaurs existed long before birds and chickens. Fossilized eggs dating back on hundred millions years have been uncovered. Thus it can be said that eggs came before chickens.";
}
private void Challenge2()
{
DifficultyValue.Text = "25%";
CompletionValue.IsChecked = false;
TitleValue.Text = "Halving Seven";
QuestionValue.Text = "Can you prove that seven is half of twelve?";
bmp.UriSource = new Uri("Images/Challenge2.png", UriKind.Relative);
ImageValue.Source = bmp;
ImageValue.Visibility = Visibility.Visible;
ResourceValue.Text = "Resource: Yahoo Questions";
AnswerValue.Text = "Roman numeral for 12 - XII \n Cut the roman numeral in half. you will get VII, which is 7.";
}
private void Challenge3()
{
DifficultyValue.Text = "25%";
CompletionValue.IsChecked = false;
TitleValue.Text = "Three-coin flip";
QuestionValue.Text = "You ask a friend about probability, and he tells you the following: The odds of three tossed coins turning up all heads or all tails is one in two, that is, fifty-fifty. That’s because anytime you toss three coins, at least two must match, either two heads or two tails. So that means the third coin—which is equally likely to be heads or tails—determines the odds.” Is your friend right? If not, what are the odds of three tossed coins turning up all heads or all tails?";
bmp.UriSource = new Uri("Images/Challenge3.png", UriKind.Relative);
ImageValue.Source = bmp;
ImageValue.Visibility = Visibility.Visible;
ResourceValue.Text = "Resource: Brain Games";
AnswerValue.Text = "Answer will be available soon";
}

Your challenges look awfully similar to each other, right? This is a case where you want to extract out a common data structure, and have each challenge represented as a piece of data.
With a uniform representation for your challenges, you set up the UI based on the challenge data for a particular challenge ID.
It is always possible to move your data into XML files, JSON files, or a database, but first see if the simple C# solution works for you:
// Note: This example is simplified for readability
// Here is the common data structure that can represent all challenges
private class Challenge
{
public string Title { get; set; }
public string Question { get; set; }
public string ImagePath { get; set; }
}
// All of the challenges are defined right here, as simple data
private Challenge[] _allChallenges = new Challenge[]
{
new Challenge
{
Title = "Chicken or Egg?",
Question = "Can you answer the ancient question: Which came first the chicken of the egg?",
ImagePath = "Images/Challenge1.png",
},
new Challenge
{
Title = "Halving Seven?",
Question = "Can you prove that seven is half of twelve?",
ImagePath = "Images/Challenge1.png",
},
}
// Choosing challenges is as simple as indexing into the array
private void Load()
{
int random = rand.Next(1, 4);
Challenge chosenChallenge = _allChallenges[random];
LoadChallenge(chosenChallenge);
}
// Setting up the UI for a challenge means extracting information from the data structure
private void LoadChallenge(Challenge chosenChallenge)
{
TitleValue.Text = chosenChallenge.Title;
QuestionValue.Text = chosenChallenge.Text;
bmp.UriSource = new Uri(chosenChallenge.ImagePath, UriKind.Relative);
ImageValue.Source = bmp;
ImageValue.Visibility = Visibility.Visible;
}
You can consider this as a form of declarative programming. An important part of your program, the challenges themselves, have been converted from imperative statements (setting UI properties) into very simple data declarations.
By making this conversion, you can even check each challenge to make sure that all of the parts are filled out. Then you'll be sure that the title, question, resource, answer, etc. is set for each of your 300 challenges.

You can save the challenges in a database or a file. I do see you are using a random number and display only 1 challenge. The DB can be something like
ChallengeId, DifficultyValue, TitleValue ...
The ChallengeId will be the questionId number. So depending on the random number generated you can choose the particular ChallengeId and the relevant data.

What you should really look into is encapsulation and polymorphic code. By encapsulating your like properties into a single class, you have a better way of representing the "Challenge" as a whole, and being able to reuse the parts that you have to type over and over again (.Text = "...") will make your future coding life infinitely better. Granted, even coding the list of Challenge entities, as I have below, would be not fun, you have to enter that data somewhere sometime. We're just going to consider this a coding exercise, you could easily adapt the code below to populate _challenges from a database or serialized file.
public class Challenge
{
public int Id {get;set;}
public int Difficulty {get;set;}
public bool IsCompleted {get;set;}
public string Title {get;set;}
public string Question {get;set;}
public string Answer {get;set;}
}
public class MainPage
{
private List<Challenge> _challenges;
private Random rand = new Random();
public MainPage()
{
_challenges = new List<Challenge> {
new Challenge {
Id = 1,
Difficulty = 20,
Title = "What came first?",
Question = "The chicken or the egg?",
Answer = "The egg." },
new Challenge {
Id = 2,
Difficulty = 30,
Title = "Make 7 from 12?",
Question = "Can you prove 7 is half of 12?",
Answer = "VII" }};
}
public void LoadChallenge(Challenge challenge)
{
Difficulty.Test = String.Format("%{0}", callenge.Difficulty);
Completeted.Value = challenge.IsCompleted;
Title.Test = challenge.Title;
// etc
}
public void StartNewChallenge()
{
Challenge nextChallenge = null;
while(nextChallenge == null)
{
var nextId = rand.Next(1,2);
nextChallenge = _challenges
.Where(x => x.Id == nextId && !x.IsCompleted)
.SingleOrDefault(); // default to null if completed == true
}
LoadChallenge(nextChallenge);
}
}

Yet another alternative might be some kind of factory method:
MyForm.cs
public class MyForm
{
Random rand = new Random();
// Constructor
public MainPage()
{
InitializeComponent();
AnswerValue.Visibility = Visibility.Collapsed;
Load();
}
private void Load()
{
int random = rand.Next(1, 4);
DisplayChallenge(ChallengeFactory.GetChallenge(random));
}
private void DisplayChallenge(ChallengeFactory.Challenge challengeToDisplay)
{
DifficultyValue.Text = String.Format("{0}%", challengeToDisplay.Difficulty);
CompletionValue.IsChecked = challengeToDisplay.IsChecked;
TitleValue.Text = challengeToDisplay.Title;
QuestionValue.Text = challengeToDisplay.Question;
ImageValue.Source = challengeToDisplay.ImageSource;
ImageValue.Visibility = challengeToDisplay.Visible;
ResourceValue.Text = challengeToDisplay.ResourceValue;
AnswerValue.Text = challengeToDisplay.Answer;
}
}
ChallengeFactory.cs
public static class ChallengeFactory
{
public class Challenge
{
public int Difficulty { get; set; }
public bool IsChecked { get; set; }
public string Title { get; set; }
public string Question { get; set; }
public Uri ImageSource { get; set; }
public bool Visible { get; set; }
public string ResourceValue { get; set; }
public string Answer { get; set; }
private Challenge(int difficulty, bool isChecked, string title, string question, Uri imageSource, bool visible, string resourceValue, string answer)
{
// assign each of the arguments to the instance properties
}
}
public static Challenge GetChallenge(int challengeNumber)
{
switch(challengeNumber)
{
case 1:
return new Challenge(20, false, "Chicken or Egg?", "Can you answer the ancient question: Which came first the chicken of the egg?", new Uri("Images/Challenge1.png", UriKind.Relative), true, "Resource: Brain Games", "The Egg...");
break;
case 2:
// new challenge for challenge #2
break;
case 3:
// new challenge for challenge #3
break;
}
}
}
Note that I have made the Challenge class a nested class inside of the Factory class. The good thing about doing this is that you can make the constructor of the challenge private (meaning that you cannot create "invalid" types of challenges through anything but the factory method. The bad thing about doing this is that you have to further qualify the Challenge class by prefixing it with it's containing class, that is, ChallengeFactory. I think it's worth the extra qualifier in this case.
Ultimately I think you are stuck having to create a switch SOMEWHERE if you plan on defining all of your challenges in code. As others have said, you can significantly reduce the amount of code you need to write (and thus, the switch) by defining your challenges in an external data source (such as a database) and having a single method to read, parse, and create a Challenge instance.

Related

c# how to decide whether to add to sub-list or start new one

I have an sql query that provides me my data where I sometimes have lines that should be clustered (the data is aligned with an order by). The data is grouped by the field CAPName. Going through those rows line by line, I need to decide whether a new list should be initiated (content of CAPName differs to previous itteration), or whether the (already) initated list (from the previous iteration) should be added, too.
My pain lays with the location of the declaration of the relatedCapabilitySystem list.
I wanted to declare it within the if statement (Because, as I stated I need to decide whether the list from the previous iteration should be added too, or whether it should start a new list), but I can't as the compiler throws an exception, as the RLCapSys.Add(rCs); is non-existing in this content (which is only theoretically true). I understand why the compiler throws this exception. But if I declare the list on a "higher" level, than I always have a new list, which I don't want in the case that the item should be added to the list defined in the iteration(s) (1 or more) before
So what I want to achieve is, generate the list RLCapSys and add to it, in case the previous iteration contains the same CAPName (for clustering), otherwise create a new list.
SqlCommand cmdDetail = new SqlCommand(SQL_SubSytemsToCapability, DBConDetail);
SqlDataReader rdrDetail = cmdDetail.ExecuteReader();
List<relatedCapility> RLCaps = new List<relatedCapility>();
string lastCapShown = null;
while (rdrDetail.Read())
{
List<relatedCapabilitySystem> RLCapSys = new List<relatedCapabilitySystem>();
if (lastCapShown != rdrDetail["CAPName"].ToString())
{
//List<relatedCapabilitySystem> RLCapSys2 = new List<relatedCapabilitySystem>();
relatedCapility rC = new relatedCapility
{
Capability = rdrDetail["CAPName"].ToString(),
systemsRelated = RLCapSys,
};
RLCaps.Add(rC);
}
relatedCapabilitySystem rCs = new relatedCapabilitySystem
{
system = rdrDetail["name"].ToString(),
start = rdrDetail["SysStart"].ToString(),
end = rdrDetail["SysEnd"].ToString(),
};
RLCapSys.Add(rCs);
// method to compare the last related Capability shown create a new related Capabilty entry or add to the existing releated Capabilty related system list
lastCapShown = rdrDetail["CAPName"].ToString();
}
DBConDetail.Close();
and for reason of completness (but I think it is not needed here):
internal class CapabilitiesC
{
public List<Capability>Capabilities{ get;set;}
}
public class Capability
{
public string name { get; internal set; }
public string tower { get; internal set; }
public string color { get; internal set; }
public List<relatedCapility> related { get; set; }
}
public class relatedCapility
{
public string Capability { get; set; }
public List<relatedCapabilitySystem> systemsRelated { get; set; }
}
public class relatedCapabilitySystem
{
public string system { get; set; }
public string start { get; set; }
public string end { get; set; }
}
The purpose of your code is to take the input data and group it by capability. However, that is not immediately obvious. You can change your code to use LINQ so it becomes easier to understand and in the process solving your problem.
First you need a type to represent a record in your database. For lack of better name I will use Record:
class Record
{
public string System { get; set; }
public string Start { get; set; }
public string End { get; set; }
public string Capabilty { get; set; }
}
You can then create an iterator block to return all the records from the database (using an OR mapper like Entity Framework avoids most of this code and you can even shift some of the work from your computer to the database server):
IEnumerable<Record> GetRecords()
{
// Code to create connection and command (preferably in a using statement)
SqlDataReader rdrDetail = cmdDetail.ExecuteReader();
while (rdrDetail.Read())
{
yield return new Record {
System = rdrDetail["name"].ToString(),
Start = rdrDetail["SysStart"].ToString(),
End = rdrDetail["SysEnd"].ToString(),
Capability = rdrDetail["CAPName"].ToString()
};
}
// Close connection (proper using statement will do this)
}
Finally, you can use LINQ to perform the grouping:
var RLCaps = GetRecords()
.GroupBy(
record => record.Capability,
(capability, records) => new relatedCapility
{
Capability = capability ,
systemsRelated = records
.Select(record => new relatedCapabilitySystem
{
system = record.System,
start = record.Start,
end = record.End
})
.ToList()
})
.ToList();
Why not just assign it as NULL. The pattern would be
List<> myList = null;
if(condition)
{
myList = new List<>();
}
else
{
myList = previousList;
}
myList.Add();
previousList = myList;
I've got it working now. Thx everyone for your help. #martin, thx for your solution, you have put quite some effort into this, but that would have required for me to completely re-write my code. I am sure your approach would work and will be my next approach should I have a similar problem again.
It was a combination of the other answers that helped me figure it out. Let me show you what I ended up with:
SqlCommand cmdDetail = new SqlCommand(SQL_SubSytemsToCapability, DBConDetail);
SqlDataReader rdrDetail = cmdDetail.ExecuteReader();
List<relatedCapility> RLCaps = new List<relatedCapility>();
List<relatedCapabilitySystem> RLCapSys = new List<relatedCapabilitySystem>();
string lastCapShown = null;
while (rdrDetail.Read())
{
if (lastCapShown != rdrDetail["CAPName"].ToString())
{
RLCapSys = relatedCapabilitySystemList();
relatedCapility rC = new relatedCapility
{
Capability = rdrDetail["CAPName"].ToString(),
systemsRelated = RLCapSys,
};
RLCaps.Add(rC);
}
relatedCapabilitySystem rCs = new relatedCapabilitySystem
{
system = rdrDetail["name"].ToString(),
start = rdrDetail["SysStart"].ToString(),
end = rdrDetail["SysEnd"].ToString(),
};
RLCapSys.Add(rCs);
// method to compare the last related Capability shown create a new related Capabilty entry or add to the existing releated Capabilty related system list
lastCapShown = rdrDetail["CAPName"].ToString();
}
DBConDetail.Close();
So that's the section already shown bevor including my changes. Plus I added this:
private List<relatedCapabilitySystem> relatedCapabilitySystemList()
{
List<relatedCapabilitySystem> RLCapSys = new List<relatedCapabilitySystem>();
return RLCapSys;
}
Now I have new list reference everytime the CapName changes that is then added to the "higher" list. Before I had the issue of the very same list repeatedly assigned rather than a fresh one started. So thx again for your effort.

Storing multiple type of data

I'm trying to create a quest system. I have QuestCreator, Quest and multiple objective classes that inherits an interface(TalkObjective, LocationObjective etc.)
In Quest class' constructor, I have created a list like List<IObjective>.
It didn't work.
Then I created a class to hold all different types of lists. But I lost the ability of ordering my objectives.
My question is; Is there a better way/design to do that?
[Edit]
I'm sorry that I didn't detailed it enough. Since I changed my code, I can't post it here. I tried to create the same code but this time the code is not giving me error. So I solve the problem on my own.
I was using a tutorial that wasn't completed/abandoned.
Here is the link to github
I built my Item/Inventory system with abstract classes and it was the first thing that came to my mind. But my intention was to create this quest system the way creator of the tutorial designed, so that I can learn his way.
I wanted to put objects of different Objective Classes in a list with the interface that they using in common way.
public class QuestCreator : MonoBehaviour {
#region fields
private List<IQuestObjective> objectives;
private GameObject itemManager;
private ItemDatabase itemdb;
private Location location;
private Quest quest;
//For Saving Quest
private Quest_data quests;
#endregion
void Update()
{
//Just for the test purpose
if (Input.GetKeyDown (KeyCode.E))
{
itemManager = GameObject.Find ("GameManager");
itemdb = itemManager.GetComponent<ItemDatabase>();
Item item = new Item ();
Item item2 = new Item ();
item = itemdb.weapon_database[0];
item2 = itemdb.weapon_database [1];
CollectionObjective collectionObjective = new CollectionObjective ("Find", 3, item, "Find this precious item");
CollectionObjective collectionObjective2 = new CollectionObjective ("Find", 1, item2, "Find Sword of Warrior from Dark Passage");
LocationObjective locationObjective = new LocationObjective ("Go to Green Valley", "Go to " + location, location, false);
objectives = new List<IQuestObjective> ();
objectives.Add(collectionObjective);
objectives.Add (collectionObjective2);
objectives.Add (locationObjective);
QuestText questText = new QuestText ();
QuestIdentifier questIdentifier = new QuestIdentifier();
questText.Title = "Finding Sword of Warrior";
questText.DescriptionSummary = "Summary...";
questText.Hint = "Hint...";
questIdentifier.QuestID = 1;
questIdentifier.SourceID = 1;
quest = new Quest (questIdentifier, questText, objectives);
Debug.Log (quest.Objectives[1].Description);
Debug.Log (quest.Objectives.Count);
}
}
You need to look into inheritance and polymorphism.
In your case you'd have a IObjective class that contains all the common logic:
public abstract IObjective : MonoBehaviour
{
public abstract void CommonMethod();
public virtual void OverrideIfNeeded(){}
public void UseAsIs(){}
}
CommonMethod has to be overrriden by subclass. OverrideIfNeeded may be overriden or used as it is. UseAsIs cannot be overriden (it can be hidden though).
Then you have a collection:
IEnumerable<IObjective> collection;
it contains all kind of different objects that are all IObjective and you can iterate and call all methods from the IObjective:
foreach(IObjective obj in collection)
{
obj.CommonMethod();
obj.UseAsIs();
...
}
Here is an example of the code you may have for your problem.
public class Program
{
public struct Location
{
// Assumes 2D game location
public int X;
public int Y;
}
public struct Character
{
public int GameCharId;
}
public class BaseObjective
{
public string Title;
public string Description;
}
public class TalkObjective : BaseObjective
{
public Character TargetCharacter;
}
public class LocationObjective : BaseObjective
{
public Location TargetLocation;
}
public static void Main(string[] args)
{
List<BaseObjective> currentObjectives = new List<BaseObjective>();
TalkObjective obj1 = new TalkObjective(){ Title = "Talk to Bob", Description = "Bob has some useful information for you", TargetCharacter = new Character(){GameCharId = 87}};
LocationObjective obj2 = new LocationObjective(){ Title = "Find the thing", Description = "Bob informed you of a thing, go and find it", TargetLocation = new Location(){ X = 33, Y=172}};
currentObjectives.Add(obj1);
currentObjectives.Add(obj2);
}
}
At first to all game programming, I'm highly suggesting going through this web book - http://gameprogrammingpatterns.com/contents.html (it also offers PDF/book variant for some money). It helps You with some game-pattern examples and You will get an idea how games are made.
Your question is kind of broad and related to each person opinion, which should not be listed as a Q on SO, however:
Logically to me it is like: There is 1 Quest (created by factory QuestCreator), which contains List<Objectives>.
Objective should be an abstract class, containing some variables and methods (Is objective done? - other things that all Objectives have in common).
After that You should inherit smaller objective (like TalkObjective, ItemObjective) and override inner implementation of the methods -> IsObjectiveDone.
On
To be honest, in game-programming, developers are stepping away to avoid inheritance as much as possible. It is too hard to create inheritance tree and then go through the code. Instead they are trying to rely on pattern like Component (same source as above).
Adding some example:
public abstract class Objective
{
public bool IsObjectiveDone { get; private set; }
public virtual void CheckIfDone();
}
public class ObjectiveGroup
{
public bool AllObjectivesDone => SubObjectives.All(a => a.IsObjectiveDone);
public Objective[] SubObjectives { get; private set; }
public static ObjectiveGroup Create(TypeOfQuest aType, Requirements[] aReq)
{ /* factory implementation */ }
}
Once You have the example above, You can define each type of "special" objective:
public class ItemObjective : Objective
{
public Item RequiredItem { get; private set; }
override public void CheckIfDone()
{
this.IsObjectiveDone = Player.GetInstance().Inventory.Contains(RequiredItem);
}
}
Once You will want to start new Quest, You will call the factory, which will create the Quest, containing group of objectives. On each objective You will CheckIfDone everytime user do some action/get new item or so.
public class Quest
{
public ObjectiveGroup { get; private set; }
public Quest(TypeOfQuest aType, Requirements[] aReq)
{
this.ObjectiveGroup = ObjectiveGroup.Create(aType, aReq);
}
}
public class Player
{
public List<Quest> Quests = new List<Quest>();
public List<Item> Inventory = new List<Item>();
}
public void Main(/* ... */)
{
Player player = new Player();
player.Quests.Add(new Quest(TypeOfQuest.ItemObtain, new Requirements[] { Item["Sword of Conan"] });
while(true)
{
player.Quests.ObjectiveGroup.ForEach(a => a.SubObjectives.ForEach(b => b.CheckIfDone()));
foreach(var objGrp in player.Quests.ObjectiveGroup)
if(objGrp.IsObjectiveDone) Console.WriteLine("Quest completed");
}
}
The better design, will be using Finite Automaton for this task, not some sort of list of objective.
So, your quest will be described with graph of predicates (conditions where to move to state or not, event listeners if you want) and states (accompishments in quest). For example, let's imagine hero entered some tavern, then he also enters some different quest lines. One of them describes town robber quest:
[Start] -(talked with barmen about robber)-> [Kill robber]
[Start] -(talked with robber wife) -> [Ask robber to return items]
//this is made for karma decision between two quest lines, so you are free to chose what to do with poor robber, take robber money or gain karma in town.
[Ask robber to return items] -(talked with barmen about robber)-> [Kill robber]
[Kill robber] -(talked with robber wife) -> [Ask robber to return items]
//barmen quest line
[Kill robber] -(robber killed)-> [Success quest (you can take money as reward)]
[Kill robber] -(robber spared)-> [Fail quest]
//wife quest line
[Ask robber to return items] -(robber convinced)-> [Success quest (you can now sleep with his wife for free)]
[Ask robber to return items] -(robber not convinced)-> [Ask robber to return items]
[Ask robber to return items] -(robber got bored of your questions)-> [Fail quest]
As you see this is all described with simple automaton rules and you can make pretty complex quests without much effort. In case of your list of objective you can't possibly branch your quest into different states, so only possible way to complete your quest is to meet ALL actions described one by one, even if it has two possible and successful outcomes.
Predicates in this example can be described as events, and states - as simple numbers or strings.
This is just very slow example of how I see it:
public class QAutomaton
{
private readonly Dictionary<string, Dictionary<string, string>> _graph = new Dictionary<string, Dictionary<string, string>>();
public void AddState(string state)
{
_graph.Add(state, new Dictionary<string, string>());
}
public void AddCondition(string from, string condition, string to)
{
_graph[from].Add(condition, to);
}
public string GetNext(string from, string condition)
{
var conds = _graph[from];
string nextState;
conds.TryGetValue(condition, out nextState);
return nextState;
}
}
public class Quest
{
public string CurrentState = "Start";
private readonly QAutomaton _automaton;
public Quest(QAutomaton automaton)
{
_automaton = automaton;
}
public void FeedEvent(string condition)
{
var nextState = _automaton.GetNext(CurrentState, condition);
if (nextState != null)
{
CurrentState = nextState;
}
}
}
public static void Main()
{
var fa = new QAutomaton();
fa.AddState("Start");
fa.AddState("Kill robber");
fa.AddState("Ask robber to return items");
fa.AddCondition("Start", "talked with barmen about robber", "Kill robber");
fa.AddCondition("Start", "talked with robber wife", "Ask robber to return items");
//describe rest here...
_quest = new Quest(fa);
}
public static void OnTalkedWithBarmenAboutRobberEventHandler()
{
_quest.FeedEvent("talked with barmen about robber");
var state = _quest.CurrentState;
if (state == "Kill robber")
{
//locate him on global map or something
}
}

Why is my game serializing this class?

So I'm making a game, and it saves users' progress on the computer in a binary file. The User class stores a few things:
Integers for stat values (Serializable)
Strings for the Username and the skin assets
Lists of both the Achievement class and the InventoryItem class, which I have created myself.
Here are the User fields:
public string Username = "";
// ID is used for local identification, as usernames can be changed.
public int ID;
public int Coins = 0;
public List<Achievement> AchievementsCompleted = new List<Achievement>();
public List<InventoryItem> Inventory = new List<InventoryItem>();
public List<string> Skins = new List<string>();
public string CurrentSkinAsset { get; set; }
The Achievement class stores ints, bools, and strings, which are all serializable. The InventoryItem class stores its name (a string) and an InventoryAction, which is a delegate that is called when the item is used.
These are the Achievement class's fields:
public int ID = 0;
public string Name = "";
public bool Earned = false;
public string Description = "";
public string Image;
public AchievmentDifficulty Difficulty;
public int CoinsOnCompletion = 0;
public AchievementMethod OnCompletion;
public AchievementCriteria CompletionCriteria;
public bool Completed = false;
And here are the fields for the InventoryItem class:
InventoryAction actionWhenUsed;
public string Name;
public string AssetName;
The source of the InventoryAction variables are in my XNAGame class. What I mean by this is that the XNAGame class has a method called "UseSword()" or whatever, which it passes into the InventoryItem class. Previously, the methods were stored in the Game1 class, but the Game class, which Game1 inherits from, is not serializable, and there's no way for me to control that. This is why I have an XNAGame class.
I get an error when trying to serialize: "The 'SpriteFont' class is not marked as serializable", or something like that. Well, there is a SpriteFont object in my XNAGame class, and some quick tests showed that this is the source of the issue. Well, I have no control over whether or not the SpriteFont class is Serializable.
Why is the game doing this? Why must all the fields in the XNAGame class be serializable, when all I need is a few methods?
Keep in mind when answering that I'm 13, and may not understand all the terms you're using. If you need any code samples, I'll be glad to provide them for you. Thanks in advance!
EDIT: One solution I have thought of is to store the InventoryAction delegates in a Dictionary, except that this will be a pain and isn't very good programming practice. If this is the only way, I'll accept it, though (Honestly at this point I think this is the best solution).
EDIT 2: Here's the code for the User.Serialize method (I know what I'm doing in inefficient, and I should use a database, blah, blah, blah. I'm fine with what I'm doing now, so bear with me.):
FileStream fileStream = null;
List<User> users;
BinaryFormatter binaryFormatter = new BinaryFormatter();
try
{
if (File.Exists(FILE_PATH) && !IsFileLocked(FILE_PATH))
{
fileStream = File.Open(FILE_PATH, FileMode.Open);
users = (List<User>)binaryFormatter.Deserialize(fileStream);
}
else
{
fileStream = File.Create(FILE_PATH);
users = new List<User>();
}
for (int i = 0; i < users.Count; i++)
{
if (users[i].ID == this.ID)
{
users.Remove(users[i]);
}
}
foreach (Achievement a in AchievementsCompleted)
{
if (a.CompletionCriteria != null)
{
a.CompletionCriteria = null;
}
if (a.OnCompletion != null)
{
a.OnCompletion = null;
}
}
users.Add(this);
fileStream.Position = 0;
binaryFormatter.Serialize(fileStream, users);
You cannot serialize a SpriteFont by design, actually this is possible (.XNB file) but it hasn't been made public.
Solution:
Strip it off your serialized class.
Alternatives:
If for some reasons you must serialize some font, the first thing that comes to my mind would be to roll-out your own font system such as BMFont but that's a daunting task since you'll have to use it everywhere else where you might already do ...
Generate a pre-defined amount of fonts (i.e. Arial/Times/Courier at size 10/11/12 etc ...) using XNA Content app (can't recall its exact name); then store this user preference as two strings. With a string.Format(...) you should be able to load the right font back quite easily.
Alternative 2 is certainly the easiest and won't take more than a few minutes to roll-out.
EDIT
Basically, instead of saving a delegate I do the following:
inventory items have their own type
each type name is de/serialized accordingly
their logic does not happen in the main game class anymore
you don't have to manually match item type / action method
So while you'll end up with more classes, you have concerns separated and you can keep your main loop clean and relatively generic.
Code:
public static class Demo
{
public static void DemoCode()
{
// create new profile
var profile = new UserProfile
{
Name = "Bill",
Gold = 1000000,
Achievements = new List<Achievement>(new[]
{
Achievement.Warrior
}),
Inventory = new Inventory(new[]
{
new FireSpell()
})
};
// save it
using (var stream = File.Create("profile.bin"))
{
var formatter = new BinaryFormatter();
formatter.Serialize(stream, profile);
}
// load it
using (var stream = File.OpenRead("profile.bin"))
{
var formatter = new BinaryFormatter();
var deserialize = formatter.Deserialize(stream);
var userProfile = (UserProfile) deserialize;
// set everything on fire :)
var fireSpell = userProfile.Inventory.Items.OfType<FireSpell>().FirstOrDefault();
if (fireSpell != null) fireSpell.Execute("whatever");
}
}
}
[Serializable]
public sealed class UserProfile
{
public string Name { get; set; }
public int Gold { get; set; }
public List<Achievement> Achievements { get; set; }
public Inventory Inventory { get; set; }
}
public enum Achievement
{
Warrior
}
[Serializable]
public sealed class Inventory : ISerializable
{
public Inventory() // for serialization
{
}
public Inventory(SerializationInfo info, StreamingContext context) // for serialization
{
var value = (string) info.GetValue("Items", typeof(string));
var strings = value.Split(';');
var items = strings.Select(s =>
{
var type = Type.GetType(s);
if (type == null) throw new ArgumentNullException(nameof(type));
var instance = Activator.CreateInstance(type);
var item = instance as InventoryItem;
return item;
}).ToArray();
Items = new List<InventoryItem>(items);
}
public Inventory(IEnumerable<InventoryItem> items)
{
if (items == null) throw new ArgumentNullException(nameof(items));
Items = new List<InventoryItem>(items);
}
public List<InventoryItem> Items { get; }
#region ISerializable Members
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
var strings = Items.Select(s => s.GetType().AssemblyQualifiedName).ToArray();
var value = string.Join(";", strings);
info.AddValue("Items", value);
}
#endregion
}
public abstract class InventoryItem
{
public abstract void Execute(params object[] objects);
}
public abstract class Spell : InventoryItem
{
}
public sealed class FireSpell : Spell
{
public override void Execute(params object[] objects)
{
// using 'params object[]' a simple and generic way to pass things if any, i.e.
// var world = objects[0];
// var strength = objects[1];
// now do something with these !
}
}
Okay, so I figured it out.
The best solution was to use a Dictionary in the XNAGame class, which stores two things: an ItemType (an enumeration), and an InventoryAction. Basically, when I use an item, I check it's type and then look up it's method. Thanks to everyone who tried, and I'm sorry if the question was confusing.

List of Anonymous Functions or Thousand of Classes

I wish to create an online quiz that can ask any question from thousands of programmed questions. Each question is created via a function that is given an array of int whose values determine the exact question displayed. I have each question as a class:
public class AddingTwoDigitNumbers : IQuestion
{
public string QName() { return "Adding Two-Digit Numbers" };
public int[] QParams() { return int[]() {Random(10, 99), Random(10, 99) };
public void Question(int[] values) {
Console.WriteLine(string.Format("What is {1} + {2}?", values[0], values[1]);
}
public void Answer(int[] values) {
Console.WriteLine(values[0] + values[1]).ToString());
}
}
QParams creates the array of int (to determine exactly the question created), that is given to both Question and Answer to create the question and answer.
I want a List of questions searchable by QName but would rather not have to create (and name) thousands of classes all implementing IQuestion.
So here is my second solution:
public class Question
{
public string QName { get; set; }
public Func<int[]> QParams { get; set; }
public Action<int[]> Question { get; set; }
public Action<int[]> Answer { get; set; }
}
public class QuestionRepository
{
public static Dictionary<string, Question> Questions = new Dictionary<string, Question>();
public static void AddQuestions(Question[] qs) {
foreach (Question q in qs) Questions.Add(q.QName, q);
}
}
public class FirstSetOfQuestions
{
static void AddQuestions()
{
QuestionRepository.AddQuestions(new Question[]
{
new Question()
{
QName = "Adding Two-Digit Numbers",
QParams = () => int[]() {Random(10, 99), Random(10, 99) },
Question = (v) => {Console.WriteLine(string.Format("What is {1} + {2}?", v[0], v[1]);},
Answer = (v) => {Console.WriteLine(values[0] + values[1]).ToString());}
},
new Question()
{
QName = "Subtracting Three-Digit Numbers",
QParams = () => int[]() {Random(100, 999), Random(100, 999) },
Question = (v) => {Console.WriteLine(string.Format("What is {1} - {2}?", v[0], v[1]);},
Answer = (v) => {Console.WriteLine(values[0] - values[1]).ToString());}
}
}
}
}
So my question is which is better? Do I create thousands of classes, having to provide a name for each one, or do I create thousands of anonymous functions and a class that stores these using (I assume) delegates? Is there a problem with the second solution if I have thousands of questions, or even a better way to do this?
(Obviously the questions I wish to create are much more complicated than shown here, and involve fractions, algebra etc.)
Just to get you started with fluent syntax, throwing in some stubs and ideas in there as well.
class Question
{
public string Name { get; set; }
public string QuestionFormat { get; set; }
public List<Range> Args { get; set; }
public Expression<Func<int[], int>> ValExp { get; set; }
public Question(string name, string questionFormat)
{
this.Name = name;
this.QuestionFormat = questionFormat;
this.Args = new List<Range>();
}
public Question Rand(int min, int max)
{
this.Args.Add(new Range(min, max));
return this;
}
public void Val(Expression<Func<int[], int>> exp)
{
this.ValExp = exp;
}
public CompiledQuestion Compile()
{
// Generate args in the appropriate ranges
// Evaluate the result with the ValExp
// Return a new CompiledQuestion with the information -
// basically just replacing Args, ValExp with RealArgs, Val
}
public ICoolDataObject Save()
{
}
public static Question Load(ICoolDataObject hmm)
{
}
}
class Range
{
public int Min { get; set; }
public int Max { get; set; }
public Range(int min, int max)
{
this.Min = min;
this.Max = max;
}
}
It's almost fun, creating questions now:
new Question("simple addition",
"whats {0} + {1}?")
.Rand(10, 99)
.Rand(10, 99)
.Val(v => v[0] + v[1]);
You can obviously add some validation checks to avoid bad number of arguments due to late hours of work, and use double or decimal instead of int wherever.
Both approaches are wrong. I presume you are not going to have thousands of different types of calculations. You are only going to have a dozen or a few dozen different types of calculations, operating on a huge variety of data.
So, you need to normalize your data so as to end up with about a dozen or a few dozen different well defined calculations on a database of well defined data, end then write about a dozen or a few dozen classes, one for each kind of calculation, only.
You might think that this is too complicated, and you might think that writing thousands of classes (or delegates, it does not really matter) might be a lot of work but each piece is small and easy, but trust me, you will bitterly regret doing it this way as soon as something needs to change on the interface or the implementation of all of these classes, and most chances are that something will need to change at some point in time.

creating a simple quiz app in windows phone 7

I need some help to get started with a simple quiz app on windows phone 7.
Users will have to guess what is the name of a given place in my city shown in a picture.
There will be a picture on the screen and right beneath it there will be 4 answers, if a user clicks on the correct one he will be notifed of the correct answer and then he can go to another question, same with incorrect asnwer scenario. I need to keep track of correct answers. So I need help or direction of how to set up such questions/answers game. I need the questions to be all random generated (4 random asnwers per question) and let say I will have 10 questions per game.
I'm planning to have a list that will hold my city places
cityList = new List<string>();
cityList.Add("answer1");
cityList.Add("answer2");
cityList.Add("asnwer3");
cityList.Add("answer4");
and i'm stuck, anyone that has done similar thing before please help, thanks
Something like this. I typed this in the answer box, it probably wont complile as is, but will definitely get you started.
class Question
{
string ImageURI { get; set; }
string Text { get; set; }
ObservableCollection<Answer> Answers { get; set; }
Answer CorrectAnswer { get; set; }
bool MakeGuess(Answer a) { UserGuess = a; if (a.Equals(CorrectAnswer)) { return true; } return false; }
bool AnsweredSuccessfully { get { return UserGuess != null && UserGuess.Equals(CorrectAnswer); } }
Answer UserGuess { get; set; }
}
class Answer
{
string text { get; set; }
}
class Game
{
public Game()
{
Questions = new ObservableCollection<Question>();
for (int i = 0; i < 10; i++) { Questions.Add(GenerateQuestion()); }
}
public Question GenerateQuestion()
{
Question q = new Question();
q.ImageURI = your link to image//;
q.Text = your image text//;
q.Answers = new ObservableCollection<Answer>();
q.CorrectAnswer = the correct answer//;
q.Answers.Add(CorrectAnswer);
for (int i = 0; i < 5; i++) { q.Answers.Add(GenerateAnswer(q.CorrectAnswer)); }
return q;
}
public Answer GenerateAnswer(Answer ignore)
{
List<Answer> answers = DataStore.Answers;//go to your main list of answers.
Random rand = new Random();
Answer a = answers[Random.Next(0,answers.Size())];
while (a == null || a.Equals(ignore) { a = answers[Random.Next(0,answers.Size())];}
return a;
}
public ObservableCollection<Question> Questions { get; set; }
}
I would make my own class Puzzle which would look like this one:
public class Puzzle
{
List<string> answers = new List<string>(4);
public Puzzle(string question, List<string> answers, int correctAnswer)
{
Question = question;
Answers = answers;
CorrectAnswer = correctAnswer;
}
public string Question {get; private set;}
public int CorrectAnswer {get; private set;} //or you can make CorrectAnswer a string
public List<string> Answers { get { return answers;} }
}
This class would need logic for dataBiding if you use one, maybe some interface.
Your list would look like this:
cityPlaces = new List<string>(); //guess that's a better name
var answersOne = new List<string>() { "bla1", "bla2", "bla3", "bla4"};
cityPlaces .Add("What is the blabla blabla?",answersOne,2);
I have doubts about "I need the questions to be all random generated". I think that's not the best idea for this kind of game. Better is to make the question and answers shown in random order. Use Fisher–Yates shuffle to show them in a random order (here is a C# example, the first one, second is wrong, GUID is unique not random and sort algorithm may do anything with this kind of comparator f.e. crash).
That should help you.

Categories

Resources