I need to allow the users choose their own path by picking between two simple choices displayed on their screen in order to progress to the next set of choices, until they get to one of the endings, i.e something like this should be achieved:
I have tried the following code, but only the left side is evaluated each time. I am wondering how can I achieve a results like the above image (covering all the branches)? For instance, if the user selects "No" the application shouldn't ask any further question from the user and just simply shows the "Maybe you want a Pizza" message. I have done this with decision tree algorithm and need to fix it, so that it covers both the left and right sides just like the above image.
namespace ConsoleApp3
{
class Program
{
static void Main(string[] args)
{
var decisionTree = MainDecisionTree();
var client = new Client();
Console.WriteLine("Do you want a book? true/false");
client.Answer[0] = bool.Parse(Console.ReadLine());
Console.WriteLine("Do you like it? true/false");
client.Answer[1] = bool.Parse(Console.ReadLine());
Console.WriteLine("Are you sure? true/false");
client.Answer[2] = bool.Parse(Console.ReadLine());
decisionTree.Evaluate(client);
Console.WriteLine("Press any key...");
Console.ReadKey();
}
private static DecisionQuery MainDecisionTree()
{
//Decision 2
var wantBranch = new DecisionQuery
{
Title = "Do you want a book?",
Test = (client) => client.Answer[0],
Positive = new DecisionResult { Result = true },
Negative = new DecisionResult { Result = false }
};
//Decision 1
var deserveBranch = new DecisionQuery
{
Title = "Do you like it?",
Test = (client) => client.Answer[1],
Positive = wantBranch,
Negative = new DecisionResult { Result = false }
};
//Decision 0
var sureBranch = new DecisionQuery
{
Title = "Are you sure?",
Test = (client) => client.Answer[2],
Positive = deserveBranch,
Negative = new DecisionResult { Result = false }
};
return sureBranch;
}
}
public class DecisionResult : Decision
{
public bool Result { get; set; }
public override void Evaluate(Client client)
{
Console.WriteLine("\r\nThe result: {0}", Result ? "Buy it" : "You need to wait");
}
}
public class DecisionQuery : Decision
{
public string Title { get; set; }
public Decision Positive { get; set; }
public Decision Negative { get; set; }
public Func<Client, bool> Test { get; set; }
public override void Evaluate(Client client)
{
bool result = this.Test(client);
string resultAsString = result ? "yes" : "no";
Console.WriteLine($"\t- {this.Title}? {resultAsString}");
if (result) this.Positive.Evaluate(client);
else this.Negative.Evaluate(client);
}
}
public abstract class Decision
{
public abstract void Evaluate(Client client);
}
public class Client
{
public bool[] Answer { get; set; } = new bool[3];
}
}
If I understand your issue, here is your code corrected.
I renamed some things.
I called MakeDecisionTree the method that initializes the expert system with the conditions tree and it returns the root condition.
Each condition contains a sentence to evaluate and it can be a query or a result.
For a result, the evaluate display the sentence.
For a query, the evaluate method asks the user to answer the question by yes or no. And using this answer, it calls the corresponding evaluate of the next child condition.
Sorry for my english here, it is not my native language, and I'm not involved in AI.
static private void DecisionTreeTest()
{
Console.WriteLine("Please, answer a few questions with yes or no.");
Console.WriteLine();
MakeDecisionTree().Evaluate();
}
static private bool GetUserAnswer(string question)
{
Console.WriteLine(question);
string userInput;
while ( true )
{
userInput = Console.ReadLine().ToLower();
if ( userInput == "yes" )
return true;
else
if ( userInput == "no" )
return false;
else
Console.WriteLine("Your answer is not supported, retry please." +
Environment.NewLine + Environment.NewLine +
question);
}
}
static private DecisionTreeQuery MakeDecisionTree()
{
var queryAreYouSure
= new DecisionTreeQuery("Are you sure?",
new DecisionTreeResult("Buy it."),
new DecisionTreeResult("You need to wait."),
GetUserAnswer);
var queryIsItAGoodBook
= new DecisionTreeQuery("Is it a good book?",
new DecisionTreeResult("What are you waiting for? Just buy it."),
new DecisionTreeResult("Find another one."),
GetUserAnswer);
var queryDoYouLikeIt
= new DecisionTreeQuery("Do you like it?",
queryAreYouSure,
queryIsItAGoodBook,
GetUserAnswer);
var queryDoYouWantABook
= new DecisionTreeQuery("Do you want a book?",
queryDoYouLikeIt,
new DecisionTreeResult("Maybe you want a pizza."),
GetUserAnswer);
return queryDoYouWantABook;
}
abstract public class DecisionTreeCondition
{
protected string Sentence { get; private set; }
abstract public void Evaluate();
public DecisionTreeCondition(string sentence)
{
Sentence = sentence;
}
}
public class DecisionTreeQuery : DecisionTreeCondition
{
private DecisionTreeCondition Positive;
private DecisionTreeCondition Negative;
private Func<string, bool> UserAnswerProvider;
public override void Evaluate()
{
if ( UserAnswerProvider(Sentence) )
Positive.Evaluate();
else
Negative.Evaluate();
}
public DecisionTreeQuery(string sentence,
DecisionTreeCondition positive,
DecisionTreeCondition negative,
Func<string, bool> userAnswerProvider)
: base(sentence)
{
Positive = positive;
Negative = negative;
UserAnswerProvider = userAnswerProvider;
}
}
public class DecisionTreeResult : DecisionTreeCondition
{
public override void Evaluate()
{
Console.WriteLine(Sentence);
}
public DecisionTreeResult(string sentence)
: base(sentence)
{
}
}
You decision tree design is very good, just the way you ask the user is not.
You ask all the questions in advance, and go to the decision tree later on.
Of course the tree cannot make asked questions unasked.
It's not a decision tree problem.
you would have to put these pairs of lines:
Console.WriteLine("Are you sure? true/false");
client.Answer[2] = bool.Parse(Console.ReadLine());
into the Evaluate method, or you do a callback or whatever architectural design to separate logic from user-interface.
The decision-tree has to ask, or at least know the question.
The answer does not need to be stored, you can evaluate it right after input.
Maybe you pass something to Ask the user to the evaluate Method
public interface IAsk
{
bool Question(string message);
}
And for your sample you instantiate that with
public class ConsoleAsk : IAsk
{
bool Question(string message)
{
Console.WriteLine(message);
return bool.Parse(Console.ReadLine());
}
}
and you modify your Evaluate like this:
public override void Evaluate(IAsk ask)
{
bool result = ask.Question("do you feel good or bad");
string resultAsString = result ? "yes" : "no";
Console.WriteLine($"\t- {this.Title}? {resultAsString}");
if (result) this.Positive.Evaluate(ask);
else this.Negative.Evaluate(ask);
}
of course you replace the question with your title property.
Related
My project is a bot telegram.I have a class called Mainclass.I want to test a method of it(SubmitNewsGetType).This method has no input and output.This method check message received from user and send suitable response.This is my defenition of class and method.
public class MainClass
{
enum state : int
{
_firstState = 0,
_submitNewsGetType = 1,
_submitNewsGetNews = 11,
_reservationGym = 2,
_reservationGymGetSans = 22,
_pollingForFood = 3,
_appointment = 4,
_cancelingReservation = 5,
_cancelingAppointment = 6,
Registeration = 7
};
public TelegramBot bot;
private int stateBot;
public Message sendMessage;
private string tempString;
public Message message;
private MessageTarget target;
public void SubmitNewsGetType()
{
if (message.Text.Contains("/submitnews"))
{
sendMessage = bot.SendMessage(target, "Please select a type news: \n/Elmi\n/Farhangi\n/Honari\n/Amoozeshi\n/Varzeshi\n\n/Return");
}
else if (message.Text.Contains("/Elmi") ||
message.Text.Contains("/Farhangi") ||
message.Text.Contains("/Honari") ||
message.Text.Contains("/Amoozeshi") ||
message.Text.Contains("/Varzeshi"))
{
sendMessage = bot.SendMessage(target, "Please enter your news: \n\n/Return");
stateBot = (int)state._submitNewsGetNews;
tempString = message.Text;
}
else
{
bot.SendMessage(target, "Non definded! Please select a type news: \n/Elmi\n/Farhangi\n/Honari\n/Amoozeshi\n/Varzeshi\n\n/Return");
}
}
And this is my unit test.My problem is I can't initialized Message type.
[TestMethod]
public void SubmitNewsGetNews_Test()
{
MainClass instance = new MainClass();
Message mess_output;
//How to set value for a Message type??
//This line get error
Message mess_input="/submitnews"
string expected="Please select a type news: \n/Elmi\n/Farhangi\n/Honari\n/Amoozeshi\n/Varzeshi\n\n/Return";
if (mess_input is "/submitnews")
{
a.SubmitNewsGetType();
mess_output= a.sendMessage;
Assert.AreEqual(mess_output.Text,expected);
}
else if (mess_input is "/elmi" or "/farhangi" or ...)
else if ....
}
Create interface which cover TelegramBot functionality and make TelegramBot implements it.
interface ITelegramBot
{
Message SendMessage(MessageTarget target, string msg);
}
Change type of bot member to ITelegramBot
public ITelegramBot bot;
Create "Fake" class of TelegramBot
class TelegramBotFake : ITelegramBot
{
public Message ReturnMessage { get; set; }
public string SendedMessage { get; set; }
public Message SendMessage(MessageTarget target, string msg)
{
this.SendedMessage = msg;
return this.ReturnMessage;
}
}
Test. Try to keep any logic operations away from the test methods. Any logic can contains bugs, bugs in the test method difficult to discover.
Create own test for every possible result in your method - you will need more then one...
[TestMethod]
public void SubmitNewsGetNews_MessageContainsSubmitItems_SendPleaseSelectType()
{
const string SUBMITITEMS = "/submitnews";
TelegramBotFake fakebot = new TelegramBotFake();
MainClass instance = new MainClass();
instance.bot = fakebot; //Assign our fake bot
instance.message.Text = SUBMITITEMS;
string expected = "Please select a type news: \n/Elmi\n/Farhangi\n/Honari\n/Amoozeshi\n/Varzeshi\n\n/Return";
instance.SubmitNewsGetType();
//By checking faking bot.SendedMessage we sure that right method was executed
Assert.AreEqual(instance.bot.SendedMessage, expected);
}
Try changing Message mess_input="/submitnews" to Message mess_input = new Message("/submitnews"); Also you were missing the semi colon at the line you were getting the error.
Here is a link that explains more:
https://msdn.microsoft.com/en-us/library/system.messaging.message(v=vs.110).aspx
I am in my first steps towards creating a very basic structural analysis software using Visual C#.
I decided to make it console-based (no user interface). Therefore the only way to get user's input is through chars and strings.
Imagine the user wants to create a 2D bar element. She would need to specify an initial point, a final point and a name for that bar. I want the syntax to be like follows:
"CREATE bar NAMED (bar_name) FIRST (first_point) LAST (last_point)"
Where:
(bar_name) is the name of the bar, up to the user. Let (object_name)="bar_A" (string type).
(first_point) would be the initial point of the bar. Since we are creating a 2D bar, (first_point) should be a 1x2 vector that the user should enter between parenthesis. For example, (first_point)=(0,0)
(last_point) would be the final point of the bar. Same type and syntax as (first_point).
I am just wondering if there is any easy way to achieve the string comparison task, something like comparing the user's input against a prefabricated command.
Of course without forgetting about user's input cleaning task.
I know there is a huge amount of possible solutions here. Maybe using LINQ. Maybe just using the String object. I just want to know the most efficient way, where efficient means:
The fastest the user's query gets processed, the better;
the less the lines of codes, the better; and
where thorough query sanitizing tasks are made.
This last point is really important since some user's input like this:
"CREATE bar NAMED bar_a FISRT (0,0) LAST (0,1)"
Note that the user commited a typo (FISRT instead of FIRST), and the query shouldn't run.
Thanks
Okay, I created a simple parser that should work good for you and, if the need arises, you can easily expand.
Start off by creating a new Console Application. Add a new class file called Tokenizer.cs. This file was auto generated by my TokenIcer project that I linked to you in the comments above. Make Tokenizer.cs look like this:
public class TokenParser
{
private readonly Dictionary<Tokens, string> _tokens;
private readonly Dictionary<Tokens, MatchCollection> _regExMatchCollection;
private string _inputString;
private int _index;
public enum Tokens
{
UNDEFINED = 0,
CREATE = 1,
FIRST = 2,
LAST = 3,
BAR = 4,
NAMED = 5,
BAR_NAME = 6,
WHITESPACE = 7,
LPAREN = 8,
RPAREN = 9,
COMMA = 10,
NUMBER = 11
}
public string InputString
{
set
{
_inputString = value;
PrepareRegex();
}
}
public TokenParser()
{
_tokens = new Dictionary<Tokens, string>();
_regExMatchCollection = new Dictionary<Tokens, MatchCollection>();
_index = 0;
_inputString = string.Empty;
_tokens.Add(Tokens.CREATE, "[Cc][Rr][Ee][Aa][Tt][Ee]");
_tokens.Add(Tokens.FIRST, "[Ff][Ii][Rr][Ss][Tt]");
_tokens.Add(Tokens.LAST, "[Ll][Aa][Ss][Tt]");
_tokens.Add(Tokens.BAR, "[Bb][Aa][Rr][ \\t]");
_tokens.Add(Tokens.NAMED, "[Nn][Aa][Mm][Ee][Dd]");
_tokens.Add(Tokens.BAR_NAME, "[A-Za-z_][a-zA-Z0-9_]*");
_tokens.Add(Tokens.WHITESPACE, "[ \\t]+");
_tokens.Add(Tokens.LPAREN, "\\(");
_tokens.Add(Tokens.RPAREN, "\\)");
_tokens.Add(Tokens.COMMA, "\\,");
_tokens.Add(Tokens.NUMBER, "[0-9]+");
}
private void PrepareRegex()
{
_regExMatchCollection.Clear();
foreach (KeyValuePair<Tokens, string> pair in _tokens)
{
_regExMatchCollection.Add(pair.Key, Regex.Matches(_inputString, pair.Value));
}
}
public void ResetParser()
{
_index = 0;
_inputString = string.Empty;
_regExMatchCollection.Clear();
}
public Token GetToken()
{
if (_index >= _inputString.Length)
return null;
foreach (KeyValuePair<Tokens, MatchCollection> pair in _regExMatchCollection)
{
foreach (Match match in pair.Value)
{
if (match.Index == _index)
{
_index += match.Length;
return new Token(pair.Key, match.Value);
}
if (match.Index > _index)
{
break;
}
}
}
_index++;
return new Token(Tokens.UNDEFINED, string.Empty);
}
public PeekToken Peek()
{
return Peek(new PeekToken(_index, new Token(Tokens.UNDEFINED, string.Empty)));
}
public PeekToken Peek(PeekToken peekToken)
{
int oldIndex = _index;
_index = peekToken.TokenIndex;
if (_index >= _inputString.Length)
{
_index = oldIndex;
return null;
}
foreach (KeyValuePair<Tokens, string> pair in _tokens)
{
var r = new Regex(pair.Value);
Match m = r.Match(_inputString, _index);
if (m.Success && m.Index == _index)
{
_index += m.Length;
var pt = new PeekToken(_index, new Token(pair.Key, m.Value));
_index = oldIndex;
return pt;
}
}
var pt2 = new PeekToken(_index + 1, new Token(Tokens.UNDEFINED, string.Empty));
_index = oldIndex;
return pt2;
}
}
public class PeekToken
{
public int TokenIndex { get; set; }
public Token TokenPeek { get; set; }
public PeekToken(int index, Token value)
{
TokenIndex = index;
TokenPeek = value;
}
}
public class Token
{
public TokenParser.Tokens TokenName { get; set; }
public string TokenValue { get; set; }
public Token(TokenParser.Tokens name, string value)
{
TokenName = name;
TokenValue = value;
}
}
In Program.cs, make it look like this:
class Program
{
private class Bar
{
public string Name { get; set; }
public int FirstX { get; set; }
public int FirstY { get; set; }
public int LastX { get; set; }
public int LastY { get; set; }
}
static void Main(string[] args)
{
const string commandCreateBar1 = "CREATE bar NAMED bar_a FIRST(5,10) LAST (15,20)";
const string commandCreateBar2 = "CREATE bar NAMED MyFooBar FIRST(25 , 31) LAST (153 ,210)";
const string commandCreateBar3 = "CREATE bar NAMED MySpaceyFooBar FIRST(0,0) LAST (12,39)";
Bar bar1 = ParseCreateBar(commandCreateBar1);
PrintBar(bar1);
Bar bar2 = ParseCreateBar(commandCreateBar2);
PrintBar(bar2);
Bar bar3 = ParseCreateBar(commandCreateBar3);
PrintBar(bar3);
}
private static void PrintBar(Bar bar)
{
Console.WriteLine("A new bar was Created! \"{0}\" ({1}, {2}) ({3}, {4})", bar.Name, bar.FirstX, bar.FirstY, bar.LastX, bar.LastY);
}
private static Bar ParseCreateBar(string commandLine)
{
var bar = new Bar();
var parser = new TokenParser { InputString = commandLine };
Expect(parser, TokenParser.Tokens.CREATE);
Expect(parser, TokenParser.Tokens.BAR);
Expect(parser, TokenParser.Tokens.NAMED);
Token token = Expect(parser, TokenParser.Tokens.BAR_NAME);
bar.Name = token.TokenValue;
Expect(parser, TokenParser.Tokens.FIRST);
Expect(parser, TokenParser.Tokens.LPAREN);
token = Expect(parser, TokenParser.Tokens.NUMBER);
bar.FirstX = int.Parse(token.TokenValue);
Expect(parser, TokenParser.Tokens.COMMA);
token = Expect(parser, TokenParser.Tokens.NUMBER);
bar.FirstY = int.Parse(token.TokenValue);
Expect(parser, TokenParser.Tokens.RPAREN);
Expect(parser, TokenParser.Tokens.LAST);
Expect(parser, TokenParser.Tokens.LPAREN);
token = Expect(parser, TokenParser.Tokens.NUMBER);
bar.LastX = int.Parse(token.TokenValue);
Expect(parser, TokenParser.Tokens.COMMA);
token = Expect(parser, TokenParser.Tokens.NUMBER);
bar.LastY = int.Parse(token.TokenValue);
Expect(parser, TokenParser.Tokens.RPAREN);
return bar;
}
private static Token Expect(TokenParser parser, TokenParser.Tokens expectedToken)
{
EatWhiteSpace(parser);
Token token = parser.GetToken();
if (token != null && token.TokenName != expectedToken)
{
Console.WriteLine("Expected Token " + expectedToken);
Environment.Exit(0);
}
if (token == null)
{
Console.WriteLine("Unexpected end of input!");
Environment.Exit(0);
}
return token;
}
private static void EatWhiteSpace(TokenParser parser)
{
while (parser.Peek() != null && parser.Peek().TokenPeek != null &&
parser.Peek().TokenPeek.TokenName == TokenParser.Tokens.WHITESPACE)
{
parser.GetToken();
}
}
}
As you can see, I created 3 test scenarios. Notice all white space is ignored. If you want to be strict about the white space, you can modify the EatWhiteSpace function to be strict.
If you want, I have a simple expression parser I could throw into this code too, that way you could have commands such as CREATE bar NAMED bar_a FIRST(3+2, 7*8 + 12) LAST (150-100, 12-3*2). I've got a simple expression parser I made a while back using TokenIcer that I can throw in. It can parse any math expression and supports parenthesis, add, subtract, multiply, and divide.
Tokenization is one way to go, but if you aren't planning on supporting way too many commands and parameters, you should look at Regexes.
Regex regex = new Regex(#"^CREATE bar NAMED (?<BarName>[A-Za-z0-9-_]*) FIRST (?<FirstPoint>\([0-9]+\|[0-9]+\)) LAST (?<LastPoint>\([0-9]+\|[0-9]+\)$");
Match match = regex.Match("create bar named bar_a first (0,0) last (0,1)", RegexOptions.IgnoreCase);
if (match.Success)
{
var name = match.Groups["BarName"].Value;
// and so on for other matches
}
I'll explain but codes can explain it way better than me.
I'm trying to get value of 2 variables
I create a new object at it's class and use its members which I set their values from packet.
Whenever I try to access them I find them zeros which kind of doesn't make sense to me.
Here is the important part of the code that is related to the problem
public class PacketHandler
{
.
. dots means unrelated long code
.
public PacketHandler()
{
}
public ushort actualX { get; set; }
public ushort actualY { get; set; }
.
.
.
case 10010:
{
if (BitConverter.ToUInt16(data, 8) == 1002)
{
actualX = BitConverter.ToUInt16(data, 24);
actualY = BitConverter.ToUInt16(data, 26);
}
break;
and here is the other class I'm trying to get the value at
public class ClientBase
{
GameUser role2;
PacketHandler ph = new PacketHandler(); <<<<<<<<
public ClientBase(GameUser role)
{
role2 = role;
Thread T = new Thread(HuntThread) { Name = "Hunt Thread" };
T.Start(this);
Console.WriteLine("with name : " +T.Name + " with execution context : "+T.ExecutionContext +" with state : "+ T.IsAlive + " with id : " + T.ManagedThreadId);
T.Join();
}
Monster GetNextkill()
{
Monster CurrentTarget = null;
foreach (KeyValuePair<uint, Monster> Pair in Dictionary.Clone(Dictionary.Monsters))
{
CurrentTarget = Pair.Value;
}
return CurrentTarget;
}
public void HuntThread(object Sender)
{
try
{
ClientBase Client = Sender as ClientBase;
while (true)
{
Monster Target = GetNextkill();
if (Target != null)
{
Thread.Sleep(1000);
ProxyParadise.Network.Packets.PacketStructure ps = new ProxyParadise.Network.Packets.PacketStructure();
ps.__Packet10010(Target.X, Target.Y, role2, "normal", "server", ph.actualX, ph.actualY); <<<<< i get zeros at the ph.actualX/Y
Thread.Sleep(1000);
ps.__Packet10010(Target.X, Target.Y, role2, "sdasdsa", "client", role2.X, role2.Y);
Thread.Sleep(1000);
ps.__Packet1022NONEXP(Target.UID, Target.X , Target.Y, role2);
Dictionary.Monsters.SafeRemove(Target.UID);
please forgive me if I sound stupid, and to make it more clear ,I want a proper way of transferring data through classes at a new thread.
Please try to apply it to this example for better understanding.
have a wonderful day
I am keeping track of values in a console. Two people "duel" against each other and I was using a dictionary to keep the names recorded along with damage done.
var duels = new Dictionary<string, string>();
duels.Add("User1", "50");
duels.Add("User2","34");
I'm trying to store both users in the same dictionary row, so it could be verified as User1 is dueling against User2. This way if another duel started, it would not interfere with User1 or User2.
duels.Add("KeyUser1","KeyUser2","50","34",.../*Other attributes of the duel*/);
I need two keys so I can check where the user's damage will go. The damage will always go to the other key--vice versa.
What can I do to make this work?
Thank you.
public class Duel
{
public string User1 {get; protected set;}
public string User2 {get; protected set;}
public Duel(string user1, string user2)
{
User1 = user1;
User2 = user2;
}
public HashSet<string> GetUserSet()
{
HashSet<string> result = new HashSet<string>();
result.Add(this.User1);
result.Add(this.User2);
return result;
}
//TODO ... more impl
}
Let's make some duels. CreateSetComparer allows the dictionary to use the values of the set for equality testing.
List<Duel> duelSource = GetDuels();
Dictionary<HashSet<string>, Duel> duels =
new Dictionary<HashSet<string>, Duel>(HashSet<string>.CreateSetComparer());
foreach(Duel d in duelSource)
{
duels.Add(d.GetUserSet(), d);
}
And finding a duel:
HashSet<string> key = new HashSet<string>();
key.Add("User1");
key.Add("User2");
Duel myDuel = duels[key];
You could try making a custom data type for the key:
class DualKey<T> : IEquatable<DualKey<T>> where T : IEquatable<T>
{
public T Key0 { get; set; }
public T Key1 { get; set; }
public DualKey(T key0, T key1)
{
Key0 = key0;
Key1 = key1;
}
public override int GetHashCode()
{
return Key0.GetHashCode() ^ Key1.GetHashCode();
}
public bool Equals(DualKey<T> obj)
{
return (this.Key0.Equals(obj.Key0) && this.Key1.Equals(obj.Key1))
|| (this.Key0.Equals(obj.Key1) && this.Key0.Equals(obj.Key0));
}
}
Then use a Dictionary<DualKey<string>, string>;
Something quick.
class UserScores {
public string Key { get; set; }
public int User1Score { get; set; }
public int User2Score { get; set; }
public UserScores(string username1, string username2)
{
Key = username1 + ":" + username2;
}
}
void Main()
{
var userScore = new UserScores("fooUser", "barUser");
var scores = new Dictionary<string, UserScores>();
scores.Add(userScore.Key, userScore);
// Or use a list
var list = new List<UserScores>();
list.Add(userScore);
list.Single (l => l.Key == userScore.Key);
}
Although a proper solution in my opinion would use a better thought out UserScores object that tracks that particular "duel" session.
Since a single person can be involved in at most one duel at a time, you can use a single dictionary to directly "index" both endpoints in all duels, something like this:
class Duel {
public Duel(string user1, string user2) {
Debug.Assert(user1 != user2);
User1 = user1;
User2 = user2;
}
public readonly string User1;
public readonly string User2;
public int User1Score;
public int User2Score;
}
class Program {
static void Main(string[] args) {
var dict = new Dictionary<string, Duel>();
// Add a new duel. A single duel has two keys in the dictionary, one for each "endpoint".
var duel = new Duel("Jon", "Rob");
dict.Add(duel.User1, duel);
dict.Add(duel.User2, duel);
// Find Jon's score, without knowing in advance whether Jon is User1 or User2:
var jons_duel = dict["Jon"];
if (jons_duel.User1 == "Jon") {
// Use jons_duel.User1Score.
}
else {
// Use jons_duel.User2Score.
}
// You can just as easily find Rob's score:
var robs_duel = dict["Rob"];
if (robs_duel.User1 == "Rob") {
// Use robs_duel.User1Score.
}
else {
// Use robs_duel.User2Score.
}
// You are unsure whether Nick is currently duelling:
if (dict.ContainsKey("Nick")) {
// Yup!
}
else {
// Nope.
}
// If Jon tries to engage in another duel while still duelling Rob:
var duel2 = new Duel("Jon", "Nick");
dict.Add(duel2.User1, duel); // Exception! Jon cannot be engaged in more than 1 duel at a time.
dict.Add(duel2.User2, duel); // NOTE: If exception happens here instead of above, don't forget remove User1 from the dictionary.
// Removing the duel requires removing both endpoints from the dictionary:
dict.Remove(jons_duel.User1);
dict.Remove(jons_duel.User2);
// Etc...
}
}
This is just a basic idea, you might consider wrapping this functionality in your own class...
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.