[gbrainy/gbrainy_15x] Use regular expressions & attributes to check answers



commit 036a7a00139ef8441fbd38b48e117d00c0b421fb
Author: Jordi Mas <jmas softcatala org>
Date:   Fri May 21 04:02:06 2010 +0200

    Use regular expressions & attributes to check answers

 src/Core/Main/Game.cs                          |  100 +++++++++++-
 src/Core/Main/Verbal/Analogies.cs              |   15 --
 src/Games/Calculation/CalculationRatio.cs      |   43 +----
 src/Games/Calculation/CalculationTwoNumbers.cs |   42 +----
 src/Games/Logic/PuzzleTimeNow.cs               |   28 +---
 tests/Core/GameManagerTest.cs                  |    2 +-
 tests/Core/GameTest.cs                         |  216 ++++++++++++++++++++++++
 tests/Core/PlayerPersonalRecordTest.cs         |    2 -
 tests/Makefile.am                              |    3 +-
 9 files changed, 341 insertions(+), 110 deletions(-)
---
diff --git a/src/Core/Main/Game.cs b/src/Core/Main/Game.cs
index f7e7b36..583d9f3 100644
--- a/src/Core/Main/Game.cs
+++ b/src/Core/Main/Game.cs
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007 Jordi Mas i Hernàndez <jmas softcatala org>
+ * Copyright (C) 2007-2010 Jordi Mas i Hernàndez <jmas softcatala org>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -20,6 +20,7 @@
 using System;
 using System.ComponentModel;
 using System.Collections.Generic;
+using System.Text.RegularExpressions;
 using Mono.Unix;
 
 using gbrainy.Core.Views;
@@ -48,6 +49,18 @@ namespace gbrainy.Core.Main
 			Master			= 8,
 		}
 
+		[Flags]
+		public enum AnswerCheckAttributes
+		{
+			None			= 0,
+			Trim			= 2,
+			IgnoreCase		= 4,
+			IgnoreSpaces		= 8,
+			MatchAll		= 16,
+		}
+
+		public const char AnswerSeparator = '|';
+
 		public class AnswerEventArgs : EventArgs
 		{
 			public AnswerEventArgs (string answer)
@@ -139,6 +152,15 @@ namespace gbrainy.Core.Main
 			}
 		}
 
+		// How to check the answer
+		public virtual AnswerCheckAttributes CheckAttributes {
+			get { return AnswerCheckAttributes.Trim | AnswerCheckAttributes.IgnoreCase; }
+		}
+
+		public virtual string AnswerCheckExpression {
+			get { return ".+"; }
+		}
+
 		public abstract string Name {
 			get;
 		}
@@ -362,7 +384,81 @@ namespace gbrainy.Core.Main
 
 		public virtual bool CheckAnswer (string answer)
 		{
-			return (String.Compare (answer, right_answer, true) == 0);
+			Regex regex;
+			Match match;
+			AnswerCheckAttributes type;
+			bool ignore_case, ignore_spaces;
+
+			if (String.IsNullOrEmpty (answer))
+				return false;
+
+			ignore_case = (CheckAttributes & AnswerCheckAttributes.IgnoreCase) == AnswerCheckAttributes.IgnoreCase;
+			ignore_spaces = (CheckAttributes & AnswerCheckAttributes.IgnoreSpaces) == AnswerCheckAttributes.IgnoreSpaces;
+			regex = new Regex (AnswerCheckExpression);
+
+			string [] right_answers = right_answer.Split (AnswerSeparator);
+
+			for (int i = 0; i < right_answers.Length; i++)
+			{
+				right_answers [i] = right_answers[i].Trim ();
+
+				if (ignore_spaces)
+					right_answers [i] = RemoveWhiteSpace (right_answers [i]);
+			}
+
+			if ((CheckAttributes & AnswerCheckAttributes.Trim) == AnswerCheckAttributes.Trim)
+				answer = answer.Trim ();
+
+			if (ignore_spaces)
+				answer = RemoveWhiteSpace (answer);
+
+			// All strings from the list of expected answers (two numbers: 22 | 44) must present in the answer
+			if ((CheckAttributes & AnswerCheckAttributes.MatchAll) == AnswerCheckAttributes.MatchAll)
+			{
+				match = regex.Match (answer);
+				while (String.IsNullOrEmpty (match.Value) == false)
+				{
+					for (int i = 0; i < right_answers.Length; i++)
+					{
+						if (String.Compare (match.Value, right_answers[i], ignore_case) == 0)
+						{
+							right_answers[i] = null;
+							break;
+						}
+					}
+					match = match.NextMatch ();
+				}
+
+				// Have all the expected answers been matched?
+				for (int i = 0; i < right_answers.Length; i++)
+				{
+					if (right_answers[i] != null)
+						return false;
+				}
+
+				return true;
+			}
+			else // Any string from the list of possible answers (answer1 | answer2) present in the answer will do it 
+			{
+				foreach (string s in right_answers)
+				{
+					match = regex.Match (answer);
+					if (String.Compare (match.Value, s, ignore_case) == 0)
+						return true;
+				}
+			}
+			return false;
+		}
+
+		static string RemoveWhiteSpace (string source)
+		{
+			string str = string.Empty;
+			for (int n = 0; n < source.Length; n++)
+			{
+				if (char.IsWhiteSpace (source [n]) == false)
+					str += source [n];
+			}
+			return str;
 		}
 
 		// When asking for a list of figures people trends to use spaces or commas
diff --git a/src/Core/Main/Verbal/Analogies.cs b/src/Core/Main/Verbal/Analogies.cs
index f773923..25180dd 100644
--- a/src/Core/Main/Verbal/Analogies.cs
+++ b/src/Core/Main/Verbal/Analogies.cs
@@ -198,20 +198,5 @@ namespace gbrainy.Core.Main.Verbal
 
 			return analogy;
 		}
-
-		public override bool CheckAnswer (string answer)
-		{
-			string [] items = right_answer.Split (AnalogiesFactory.Separator);
-
-			foreach (string ans in items)
-			{
-				string str = ans.Trim ();
-
-				if (String.Compare (str, answer, true) == 0)
-					return true;
-			}
-
-			return base.CheckAnswer (answer);
-		}
 	}
 }
diff --git a/src/Games/Calculation/CalculationRatio.cs b/src/Games/Calculation/CalculationRatio.cs
index 68a2662..6c12618 100644
--- a/src/Games/Calculation/CalculationRatio.cs
+++ b/src/Games/Calculation/CalculationRatio.cs
@@ -60,6 +60,14 @@ namespace gbrainy.Games.Calculation
 			get { return Catalog.GetString ("A ratio specifies a proportion between two numbers. A ratio a:b means that for every 'a' parts you have 'b' parts.");}
 		}
 
+		public override AnswerCheckAttributes CheckAttributes {
+			get { return AnswerCheckAttributes.Trim | AnswerCheckAttributes.MatchAll; }
+		}
+
+		public override string AnswerCheckExpression {
+			get { return "[0-9]+"; }
+		}
+
 		public override void Initialize ()
 		{
 			int random_max;
@@ -86,7 +94,8 @@ namespace gbrainy.Games.Calculation
 			ratio_b = 3 + random.Next (random_max);
 			number_b = number_a / ratio_a * ratio_b;
 
-			right_answer = String.Format (Catalog.GetString ("{0} and {1}"), number_a, number_b);
+			//TODO: right_answer = String.Format (Catalog.GetString ("{0} and {1}"), number_a, number_b);
+			right_answer = String.Format ("{0} | {1}", number_a, number_b);
 		}
 
 		public override void Draw (CairoContextEx gr, int area_width, int area_height, bool rtl)
@@ -103,37 +112,5 @@ namespace gbrainy.Games.Calculation
 			gr.MoveTo (x, DrawAreaY + 0.44);
 			gr.ShowPangoText (String.Format (Catalog.GetString ("have a ratio of {0}:{1}"), ratio_a, ratio_b));
 		}
-
-		public override bool CheckAnswer (string answer)
-		{	
-			string num_a = string.Empty;
-			string num_b = string.Empty;
-			bool first = true;
-		
-			for (int c = 0; c < answer.Length; c++)
-			{
-				if (answer[c] < '0' || answer[c] > '9') {
-					first = false;
-					continue;
-				}
-			
-				if (first == true)
-					num_a += answer[c];
-				else
-					num_b += answer[c];
-			}
-
-			try {
-				if (Int32.Parse (num_a) == number_a && Int32.Parse (num_b) == number_b ||
-					Int32.Parse (num_b) == number_a && Int32.Parse (num_a) == number_b)
-					return true;
-			}
-
-			catch (FormatException) {
-				return false;
-			}
-	
-			return false;
-		}
 	}
 }
diff --git a/src/Games/Calculation/CalculationTwoNumbers.cs b/src/Games/Calculation/CalculationTwoNumbers.cs
index 1489ade..db85d4e 100644
--- a/src/Games/Calculation/CalculationTwoNumbers.cs
+++ b/src/Games/Calculation/CalculationTwoNumbers.cs
@@ -61,6 +61,14 @@ namespace gbrainy.Games.Calculation
 			}
 		}
 
+		public override AnswerCheckAttributes CheckAttributes {
+			get { return AnswerCheckAttributes.Trim | AnswerCheckAttributes.MatchAll; }
+		}
+
+		public override string AnswerCheckExpression {
+			get { return "[0-9]+"; }
+		}
+
 		public override void Initialize ()
 		{
 			type = (GameTypes) random.Next ((int) GameTypes.Length);
@@ -99,7 +107,8 @@ namespace gbrainy.Games.Calculation
 
 			op2 = number_a * number_b;
 
-			right_answer = String.Format (Catalog.GetString ("{0} and {1}"), number_a, number_b);
+			//TODO: right_answer = String.Format (Catalog.GetString ("{0} and {1}"), number_a, number_b);
+			right_answer = String.Format ("{0} | {1}", number_a, number_b);
 		}
 
 		public override void Draw (CairoContextEx gr, int area_width, int area_height, bool rtl)
@@ -127,36 +136,5 @@ namespace gbrainy.Games.Calculation
 			gr.ShowPangoText (String.Format (Catalog.GetString ("number1 * number2 = {0}"), op2));
 		}
 
-		public override bool CheckAnswer (string answer)
-		{	
-			string num_a = string.Empty;
-			string num_b = string.Empty;
-			bool first = true;
-		
-			for (int c = 0; c < answer.Length; c++)
-			{
-				if (answer[c] < '0' || answer[c] > '9') {
-					first = false;
-					continue;
-				}
-			
-				if (first == true)
-					num_a += answer[c];
-				else
-					num_b += answer[c];
-			}
-
-			try {
-				if (Int32.Parse (num_a) == number_a && Int32.Parse (num_b) == number_b ||
-					Int32.Parse (num_b) == number_a && Int32.Parse (num_a) == number_b)
-					return true;
-			}
-
-			catch (FormatException) {
-				return false;
-			}
-	
-			return false;
-		}
 	}
 }
diff --git a/src/Games/Logic/PuzzleTimeNow.cs b/src/Games/Logic/PuzzleTimeNow.cs
index adf8667..7cc62dc 100644
--- a/src/Games/Logic/PuzzleTimeNow.cs
+++ b/src/Games/Logic/PuzzleTimeNow.cs
@@ -47,7 +47,6 @@ namespace gbrainy.Games.Logic
 				after, position_a, position_b, position_b));}
 		}
 
-
 		public override string Answer {
 			get {
 				string answer = base.Answer + " ";
@@ -56,6 +55,10 @@ namespace gbrainy.Games.Logic
 			}
 		}
 
+		public override AnswerCheckAttributes CheckAttributes {
+			get { return AnswerCheckAttributes.Trim | AnswerCheckAttributes.IgnoreCase | AnswerCheckAttributes.IgnoreSpaces; }
+		}
+
 		public override void Initialize ()
 		{
 			int hour;
@@ -85,28 +88,5 @@ namespace gbrainy.Games.Logic
 
 			gr.DrawTextCentered (0.5, DrawAreaY + 0.3 + figure_size, Catalog.GetString ("Sample clock"));
 		}
-
-		public override bool CheckAnswer (string answer)
-		{
-			string ans = string.Empty;
-			string user = string.Empty;
-
-			if (base.CheckAnswer (answer))
-				return true;
-
-			// Compare ignoring spaces then '1PM' is as valid as '1 PM'
-			for (int c = 0; c < answer.Length; c++)
-			{
-				if (Char.IsWhiteSpace (answer[c]) == false)
-					ans += answer[c];
-			}
-
-			for (int c = 0; c < right_answer.Length; c++)
-			{
-				if (Char.IsWhiteSpace (right_answer[c]) == false)
-					user += right_answer[c];
-			}
-			return (String.Compare (user, ans, true) == 0);
-		}
 	}
 }
diff --git a/tests/Core/GameManagerTest.cs b/tests/Core/GameManagerTest.cs
index b227e9b..1f7d246 100644
--- a/tests/Core/GameManagerTest.cs
+++ b/tests/Core/GameManagerTest.cs
@@ -31,7 +31,7 @@ namespace gbrainyTest
 		[TestFixtureSetUp]
 		public void Construct ()
 		{
-			Console.WriteLine ("GameManagerTest constructor");
+
 		}
 
 		//Lists the games without tip
diff --git a/tests/Core/GameTest.cs b/tests/Core/GameTest.cs
new file mode 100644
index 0000000..f2a5209
--- /dev/null
+++ b/tests/Core/GameTest.cs
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2010 Jordi Mas i Hernàndez <jmas softcatala org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+using System;
+using NUnit.Framework;
+
+using gbrainy.Core.Main;
+
+namespace gbrainyTest
+{
+	public class TestGame : Game
+	{
+		public string Expression { get; set; }
+		public Game.AnswerCheckAttributes Attributes { get; set; }
+
+		public TestGame ()
+		{
+			Attributes = base.CheckAttributes;
+		}
+
+		public override string Question {
+			get { return "Question"; }
+		}
+
+		public override string Name {
+			get { return "TestGame"; }
+		}
+
+		public string RightAnswer {
+			set { right_answer = value; }
+		}
+
+		public override string AnswerCheckExpression {
+			get {
+				if (String.IsNullOrEmpty (Expression))
+					return base.AnswerCheckExpression;
+
+				return Expression;
+			}
+		}
+
+		public override AnswerCheckAttributes CheckAttributes {
+			get { return Attributes; }
+		}
+
+		public override void Initialize () {}
+	}
+
+	[TestFixture]
+	public class GameTest
+	{
+		[TestFixtureSetUp]
+		public void Construct ()
+		{
+
+		}
+
+		// Test individual attributes
+		[Test]
+		public void Trim ()
+		{
+			TestGame game = new TestGame ();
+
+			game.Attributes = Game.AnswerCheckAttributes.None;
+			game.RightAnswer = "icon";
+			Assert.AreEqual (true, game.CheckAnswer ("icon"));
+			Assert.AreEqual (false, game.CheckAnswer (" icon "));
+
+			game.Attributes = Game.AnswerCheckAttributes.Trim;
+			Assert.AreEqual (true, game.CheckAnswer ("icon"));
+			Assert.AreEqual (true, game.CheckAnswer (" icon "));
+
+			game.Attributes = Game.AnswerCheckAttributes.MatchAll;
+			Assert.AreEqual (true, game.CheckAnswer ("icon"));
+			Assert.AreEqual (false, game.CheckAnswer (" icon "));
+
+			game.Attributes = Game.AnswerCheckAttributes.Trim | Game.AnswerCheckAttributes.MatchAll;
+			Assert.AreEqual (true, game.CheckAnswer ("icon"));
+			Assert.AreEqual (true, game.CheckAnswer (" icon "));
+		}
+
+		[Test]
+		public void IgnoreCase ()
+		{
+			TestGame game = new TestGame ();
+
+			game.Attributes = Game.AnswerCheckAttributes.None;
+			game.RightAnswer = "icon";
+			Assert.AreEqual (true, game.CheckAnswer ("icon"));
+			Assert.AreEqual (false, game.CheckAnswer ("ICON"));
+
+			game.Attributes = Game.AnswerCheckAttributes.IgnoreCase;
+			Assert.AreEqual (true, game.CheckAnswer ("icon"));
+			Assert.AreEqual (true, game.CheckAnswer ("ICON"));
+
+			game.Attributes = Game.AnswerCheckAttributes.MatchAll;
+			Assert.AreEqual (true, game.CheckAnswer ("icon"));
+			Assert.AreEqual (false, game.CheckAnswer ("ICON"));
+
+			game.Attributes = Game.AnswerCheckAttributes.IgnoreCase | Game.AnswerCheckAttributes.MatchAll;
+			Assert.AreEqual (true, game.CheckAnswer ("icon"));
+			Assert.AreEqual (true, game.CheckAnswer ("ICON"));
+		}
+
+		[Test]
+		public void IgnoreSpaces ()
+		{
+			TestGame game = new TestGame ();
+
+			game.Attributes = Game.AnswerCheckAttributes.None;
+			game.RightAnswer = "10 pm";
+			Assert.AreEqual (true, game.CheckAnswer ("10 pm"));
+			Assert.AreEqual (false, game.CheckAnswer ("10pm"));
+
+			game.Attributes = Game.AnswerCheckAttributes.IgnoreSpaces;
+			Assert.AreEqual (true, game.CheckAnswer ("10 pm"));
+			Assert.AreEqual (true, game.CheckAnswer ("10pm"));
+
+			game.Attributes = Game.AnswerCheckAttributes.MatchAll;
+			Assert.AreEqual (true, game.CheckAnswer ("10 pm"));
+			Assert.AreEqual (false, game.CheckAnswer ("10pm"));
+
+			game.Attributes = Game.AnswerCheckAttributes.IgnoreSpaces | Game.AnswerCheckAttributes.MatchAll;
+			Assert.AreEqual (true, game.CheckAnswer ("10 pm"));
+			Assert.AreEqual (true, game.CheckAnswer ("10pm"));
+		}
+
+		// Test attributes as used in real games
+
+		[Test]
+		public void DefaultAnswer ()
+		{
+			TestGame game = new TestGame ();
+
+			game.RightAnswer = "icon";
+			Assert.AreEqual (true, game.CheckAnswer ("icon"));
+
+			game.RightAnswer = "icona";
+			Assert.AreEqual (true, game.CheckAnswer ("icona"));
+		}
+
+		[Test]
+		public void DefaultAnswerOptions ()
+		{
+			TestGame game = new TestGame ();
+
+			game.RightAnswer = "option1 | option2";
+			Assert.AreEqual (true, game.CheckAnswer ("option1"));
+			Assert.AreEqual (true, game.CheckAnswer ("option2"));
+			Assert.AreEqual (true, game.CheckAnswer (" option2 "));
+
+			Assert.AreEqual (false, game.CheckAnswer ("option3"));
+		}
+
+		[Test]
+		public void CheckPuzzleTimeNowAnswer ()
+		{
+			TestGame game = new TestGame ();
+			game.RightAnswer = "10 PM";
+			game.Attributes = Game.AnswerCheckAttributes.Trim | Game.AnswerCheckAttributes.IgnoreCase | Game.AnswerCheckAttributes.IgnoreSpaces;
+
+			Assert.AreEqual (true, game.CheckAnswer ("10 PM"));
+			Assert.AreEqual (true, game.CheckAnswer ("10 pm"));
+			Assert.AreEqual (true, game.CheckAnswer ("10pm"));
+			Assert.AreEqual (true, game.CheckAnswer ("10Pm"));
+			Assert.AreEqual (true, game.CheckAnswer (" 10Pm "));
+
+			Assert.AreEqual (false, game.CheckAnswer ("10 P"));
+			Assert.AreEqual (false, game.CheckAnswer ("10"));
+		}
+
+		[Test]
+		public void TwoNumbersAnswer ()
+		{
+			TestGame game = new TestGame ();
+			game.RightAnswer = "10 | 20";
+			game.Expression = "[0-9]+";
+			game.Attributes = Game.AnswerCheckAttributes.Trim | Game.AnswerCheckAttributes.MatchAll;
+
+			// Right answers
+			Assert.AreEqual (true, game.CheckAnswer ("10 and 20"));
+			Assert.AreEqual (true, game.CheckAnswer ("10 i 20"));
+			Assert.AreEqual (true, game.CheckAnswer ("10 y 20"));
+			Assert.AreEqual (true, game.CheckAnswer ("10 20"));
+			Assert.AreEqual (true, game.CheckAnswer (" 10 20 "));
+
+			Assert.AreEqual (true, game.CheckAnswer ("20 and 10"));
+			Assert.AreEqual (true, game.CheckAnswer ("20 i 10"));
+			Assert.AreEqual (true, game.CheckAnswer ("20 y 10"));
+			Assert.AreEqual (true, game.CheckAnswer ("20 10"));
+			Assert.AreEqual (true, game.CheckAnswer (" 20 10 "));
+
+			// Invalid answers
+			Assert.AreEqual (false, game.CheckAnswer (" 10 30 "));
+			Assert.AreEqual (false, game.CheckAnswer ("10"));
+			Assert.AreEqual (false, game.CheckAnswer ("20"));
+			Assert.AreEqual (false, game.CheckAnswer ("10 2"));
+		}
+	}
+}
diff --git a/tests/Core/PlayerPersonalRecordTest.cs b/tests/Core/PlayerPersonalRecordTest.cs
index 2720d11..c9ebf93 100644
--- a/tests/Core/PlayerPersonalRecordTest.cs
+++ b/tests/Core/PlayerPersonalRecordTest.cs
@@ -28,8 +28,6 @@ namespace gbrainyTest
 	[TestFixture]
 	public class PlayerPersonalRecordTest
 	{
-		PlayerHistory history;
-
 		[TestFixtureSetUp]
 		public void Construct ()
 		{
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 3695e8e..93fefdb 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -10,7 +10,8 @@ CSFILES =					\
 	$(srcdir)/Core/PlayerHistoryTest.cs	\
 	$(srcdir)/Core/PlayerPersonalRecordTest.cs \
 	$(srcdir)/Core/GameSessionTest.cs \
-	$(srcdir)/Core/GameManagerTest.cs
+	$(srcdir)/Core/GameManagerTest.cs \
+	$(srcdir)/Core/GameTest.cs
 
 ASSEMBLIES = \
 	$(NUNIT_LIBS)			\



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]