1 /*
2  * Copyright (C) 2007-2011 Jordi Mas i Hernàndez <jmas@softcatala.org>
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public
15  * License along with this program; if not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 using System;
19 using System.Text.RegularExpressions;
20 using System.Text;
21 
22 using gbrainy.Core.Services;
23 
24 namespace gbrainy.Core.Main
25 {
26 	public class GameAnswer
27 	{
28 		static char separator = '|';
29 		const int MAX_POSSIBLE_ANSWER = 8;
30 		string correct;
31 		ITranslations translations;
32 
GameAnswer()33 		public GameAnswer ()
34 		{
35 			CheckAttributes = GameAnswerCheckAttributes.Trim | GameAnswerCheckAttributes.IgnoreCase;
36 			CheckExpression = ".+";
37 		}
38 
39 		// This is a property because the object can be constructed and use it
40 		// Only text related functions needed. Perhaps, a possible to do extract class pattern
41 		public ITranslations Translations {
42 			set { translations = value; }
43 			get { return translations; }
44 		}
45 
46 		public char Separator {
47 			get { return separator; }
48 		}
49 
50 		// Correct answer as shown to the user. Usually equals to Correct, but can be different
51 		// when the answer contains multiple options (e.g. 1 | 2 shown as 1 and 2).
52 		public string CorrectShow { get; set; }
53 
54 		// This is the correct answer used for validating the answer (a | b)
55 		public string Correct {
56 			get { return correct;}
57 			set {
58 				correct = value;
59 				if (CorrectShow == null) // Set default answer to show
60 					CorrectShow = correct;
61 			}
62 		}
63 
64 		public string CheckExpression { get; set; }
65 		public bool Draw { get; set; }
66 
67 		public GameAnswerCheckAttributes CheckAttributes { get; set; }
68 
GetMultiOptionsExpression()69 		public string GetMultiOptionsExpression ()
70 		{
71 			StringBuilder str = new StringBuilder ();
72 			str.Append ("[");
73 			for (int i = 0; i < MAX_POSSIBLE_ANSWER; i++)
74 				str.Append (GetMultiOptionInternal (i));
75 
76 			str.Append ("]");
77 			return str.ToString ();
78 		}
79 
80 		// Index of the option (A, B) and answer (dog, cat)
SetMultiOptionAnswer(int multioption, string answer)81 		public void SetMultiOptionAnswer (int multioption, string answer)
82 		{
83 			if (String.IsNullOrEmpty (answer) == true)
84 				throw new InvalidOperationException ("Both options should be defined");
85 
86 			string option = GetMultiOption (multioption);
87 
88 			Correct = option + Separator + answer;
89 			CorrectShow = option;
90 		}
91 
GetMultiOption(int answer)92 		public string GetMultiOption (int answer)
93 		{
94 			bool multioption;
95 
96 			multioption = (CheckAttributes & GameAnswerCheckAttributes.MultiOption) == GameAnswerCheckAttributes.MultiOption;
97 
98 			if (multioption == false)
99 				throw new InvalidOperationException ("Cannot call Multioption API if the game does not have the multioption attribute");
100 
101 			return GetMultiOptionInternal (answer);
102 		}
103 
GetMultiOptionInternal(int answer)104 		string GetMultiOptionInternal (int answer)
105 		{
106 			switch (answer) {
107 				// Translators Note
108 				// The following series of answers may need to be adapted
109 				// in cultures with alphabets different to the Latin one.
110 				// The idea is to enumerate a sequence of possible answers
111 				// For languages represented with the Latin alphabet use
112 				// the same than English
113 			case 0: // First possible answer for a series (e.g.: Figure A)
114 				return Translations.GetString ("A");
115 			case 1: // Second possible answer for a series
116 				return Translations.GetString ("B");
117 			case 2: // Third possible answer for a series
118 				return Translations.GetString ("C");
119 			case 3: // Fourth possible answer for a series
120 				return Translations.GetString ("D");
121 			case 4: // Fifth possible answer for a series
122 				return Translations.GetString ("E");
123 			case 5: // Sixth possible answer for a series
124 				return Translations.GetString ("F");
125 			case 6: // Seventh possible answer for a series
126 				return Translations.GetString ("G");
127 			case 7: // Eighth possible answer for a series
128 				return Translations.GetString ("H");
129 				// When adding new items update MAX_POSSIBLE_ANSWER accordingly
130 			default:
131 				throw new ArgumentOutOfRangeException ("Do not have an option for this answer");
132 			}
133 		}
134 
135 		// A string of for format "A, B or C
GetMultiOptionsPossibleAnswers(int num_answers)136 		public string GetMultiOptionsPossibleAnswers (int num_answers)
137 		{
138 			switch (num_answers) {
139 			case 0:
140 			case 1:
141 				throw new InvalidOperationException ("You need more than 1 answer to select from");
142 			case 2:
143 				// Translators. This is the list of valid answers, like A or B.
144 				return String.Format (Translations.GetString ("{0} or {1}"),
145 					GetMultiOption (0), GetMultiOption (1));
146 			case 3:
147 				// Translators. This is the list of valid answers, like A, B or C.
148 				return String.Format (Translations.GetString ("{0}, {1} or {2}"),
149 					GetMultiOption (0), GetMultiOption (1), GetMultiOption (2));
150 			case 4:
151 				// Translators. This is the list of valid answers, like A, B, C or D.
152 				return String.Format (Translations.GetString ("{0}, {1}, {2} or {3}"),
153 					GetMultiOption (0), GetMultiOption (1), GetMultiOption (2), GetMultiOption (3));
154 			default:
155 				throw new InvalidOperationException ("Number of multiple options not supported");
156 			}
157 		}
158 
GetFigureName(int answer)159 		public string GetFigureName (int answer)
160 		{
161 			return String.Format (Translations.GetString ("Figure {0}"), GetMultiOptionInternal (answer));
162 		}
163 
CheckAnswer(string answer)164 		public bool CheckAnswer (string answer)
165 		{
166 			Regex regex;
167 			Match match;
168 			bool ignore_case, ignore_spaces;
169 			int matched_all_in_order = 0;
170 
171 			if (String.IsNullOrEmpty (answer))
172 				return false;
173 
174 			ignore_case = (CheckAttributes & GameAnswerCheckAttributes.IgnoreCase) == GameAnswerCheckAttributes.IgnoreCase;
175 			ignore_spaces = (CheckAttributes & GameAnswerCheckAttributes.IgnoreSpaces) == GameAnswerCheckAttributes.IgnoreSpaces;
176 
177 			if (ignore_case == true) // This necessary to make pattern selection (e.g. [a-z]) case insensitive
178 				regex = new Regex (CheckExpression, RegexOptions.IgnoreCase);
179 			else
180 				regex = new Regex (CheckExpression);
181 
182 			string [] right_answers = Correct.Split (Separator);
183 
184 			for (int i = 0; i < right_answers.Length; i++)
185 			{
186 				right_answers [i] = right_answers[i].Trim ();
187 
188 				if (ignore_spaces)
189 					right_answers [i] = RemoveWhiteSpace (right_answers [i]);
190 			}
191 
192 			if ((CheckAttributes & GameAnswerCheckAttributes.Trim) == GameAnswerCheckAttributes.Trim)
193 				answer = answer.Trim ();
194 
195 			if (ignore_spaces)
196 				answer = RemoveWhiteSpace (answer);
197 
198 			// All strings from the list of expected answers (two numbers: 22 | 44) must present in the answer
199 			if ((CheckAttributes & GameAnswerCheckAttributes.MatchAll) == GameAnswerCheckAttributes.MatchAll ||
200 				(CheckAttributes & GameAnswerCheckAttributes.MatchAllInOrder) == GameAnswerCheckAttributes.MatchAllInOrder)
201 			{
202 				int pos = 0;
203 				match = regex.Match (answer);
204 				while (String.IsNullOrEmpty (match.Value) == false)
205 				{
206 					bool matched = false;
207 					if ((CheckAttributes & GameAnswerCheckAttributes.MatchAll) == GameAnswerCheckAttributes.MatchAll)
208 					{
209 						for (int i = 0; i < right_answers.Length; i++)
210 						{
211 							if (String.Compare (match.Value, right_answers[i], ignore_case) == 0)
212 							{
213 								right_answers[i] = null;
214 								matched = true;
215 								break;
216 							}
217 						}
218 						if (matched == false)
219 							return false;
220 					}
221 					else //MatchAllInOrder
222 					{
223 						if (String.Compare (match.Value, right_answers[pos++], ignore_case) == 0)
224 							matched_all_in_order++;
225 					}
226 					match = match.NextMatch ();
227 				}
228 
229 				if ((CheckAttributes & GameAnswerCheckAttributes.MatchAllInOrder) == GameAnswerCheckAttributes.MatchAllInOrder &&
230 					matched_all_in_order == right_answers.Length)
231 					return true;
232 
233 				// Have all the expected answers been matched?
234 				for (int i = 0; i < right_answers.Length; i++)
235 				{
236 					if (right_answers[i] != null)
237 						return false;
238 				}
239 
240 				return true;
241 			}
242 			else // Any string from the list of possible answers (answer1 | answer2) present in the answer will do it
243 			{
244 				foreach (string s in right_answers)
245 				{
246 					match = regex.Match (answer);
247 					if (String.Compare (match.Value, s, ignore_case) == 0)
248 						return true;
249 				}
250 			}
251 			return false;
252 		}
253 
RemoveWhiteSpace(string source)254 		static string RemoveWhiteSpace (string source)
255 		{
256 			StringBuilder str = new StringBuilder ();
257 			for (int n = 0; n < source.Length; n++)
258 			{
259 				if (char.IsWhiteSpace (source [n]) == false)
260 					str.Append (source[n]);
261 			}
262 			return str.ToString ();
263 		}
264 	}
265 }
266