1: using System.Data;
2:
3: namespace Ia.TentPlay.Cl.Model.Memorise
4: {
5: ////////////////////////////////////////////////////////////////////////////
6:
7: /// <summary publish="true">
8: /// Test Class
9: /// </summary>
10: /// <value>
11: /// https://msdn.microsoft.com/en-us/library/z1hkazw7(v=vs.100).aspx
12: /// </value>
13: /// <remarks>
14: /// Copyright © 2008-2018 Jasem Y. Al-Shamlan (info@ia.com.kw), Integrated Applications - Kuwait. All Rights Reserved.
15: ///
16: /// This library is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by
17: /// the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
18: ///
19: /// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
20: /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
21: ///
22: /// You should have received a copy of the GNU General Public License along with this library. If not, see http://www.gnu.org/licenses.
23: ///
24: /// Copyright notice: This notice may not be removed or altered from any source distribution.
25: /// </remarks>
26: public abstract class Test
27: {
28: protected static Random random = new Random(DateTime.UtcNow.Second);
29:
30: protected static int NumberOfMinutesThatMustPassBeforeAWordIsTestedAgain = 3;
31:
32: protected static TimeSpan ResponseSecondsThatIndicateMemorizationTimeSpan
33: {
34: get
35: {
36: return TimeSpan.Parse("00:00:03"); // three seconds indicate memorization
37: }
38: }
39:
40: //static int numberOfCumulativeAnswerCorrectnessIndicatorThatIndicatesRetention = 7;
41: //static int numberOfPoolWords = 7 * 7;
42: protected abstract TestTopic testTopic { get; }
43:
44: // below: order is very important
45: /// </summary>
46: public enum TestTopic { MorseCode = 0, German = 1, Math = 2, Kanji = 3, Russian = 4, Kana = 5, PhoneticAlphabet = 6 };
47:
48: /// </summary>
49: public enum TestOption { PrefereOrderByAscendingId, PreferRandom }
50:
51: /// </summary>
52: public enum AnswerType { Correct, CorrectButSlow, Incorrect }
53:
54: /*
55: * Calculate and return the proper words withing a proper question range based on the test taking history from the database
56: *
57: * - Initially all words will have Enabled = false.
58: * - The process will assign a pool of enabled words for testing.
59: * - At any time the user will be memorizing the pool of words only.
60: * - If a word is consedered memorised by its test history it will exit the word testing pool and another will be randomly added
61: */
62:
63: ////////////////////////////////////////////////////////////////////////////
64:
65: /// <summary>
66: ///
67: /// </summary>
68: public Test() { }
69:
70: ////////////////////////////////////////////////////////////////////////////
71:
72: /// <summary>
73: ///
74: /// </summary>
75: public void CalculatedTestQuestionAndOptions(TestOption testOption, out Ia.TentPlay.Cl.Model.Memorise.Score testQuestion, out List<Ia.TentPlay.Cl.Model.Memorise.Score> optionList, out string debug)
76: {
77: int testQuestionTypeId, testQuestionId, optionIndex1, optionIndex2, optionIndex3;
78: DateTime timeMinusMinutesThatMustPassBeforeAWordIsTestedAgainDateTime;
79: List<Ia.TentPlay.Cl.Model.Memorise.Score> questionList;
80:
81: debug = "";
82: optionIndex1 = optionIndex2 = optionIndex3 = 0;
83: optionList = new List<Ia.TentPlay.Cl.Model.Memorise.Score>();
84:
85: timeMinusMinutesThatMustPassBeforeAWordIsTestedAgainDateTime = DateTime.UtcNow.AddHours(3).AddMinutes(-NumberOfMinutesThatMustPassBeforeAWordIsTestedAgain);
86:
87: testQuestionTypeId = WeightedRandomTypeIdAccordingTypeDistribution(Guid.Empty);
88:
89: using (var db = new Ia.TentPlay.Db())
90: {
91: if (testOption == TestOption.PreferRandom || testOption == TestOption.PrefereOrderByAscendingId)
92: {
93: if (testOption == TestOption.PreferRandom)
94: {
95: testQuestion = (from s in db.Scores
96: where s.TestId == (int)testTopic && s.TypeId == testQuestionTypeId && s.Viewed < timeMinusMinutesThatMustPassBeforeAWordIsTestedAgainDateTime
97: orderby s.CumulativeAnswerCorrectnessIndicator, s.NumberOfTimesAsked, s.NumberOfConsecutiveCorrects, s.UserId ascending
98: select s).Take(5).FirstOrDefault();
99:
100: if (testQuestion == null)
101: {
102: testQuestion = (from s in db.Scores
103: where s.TestId == (int)testTopic && s.TypeId == testQuestionTypeId
104: orderby s.CumulativeAnswerCorrectnessIndicator, s.NumberOfTimesAsked, s.NumberOfConsecutiveCorrects, s.UserId ascending
105: select s).Take(5).FirstOrDefault();
106: }
107: }
108: else if (testOption == TestOption.PrefereOrderByAscendingId)
109: {
110: testQuestion = (from s in db.Scores
111: where s.TestId == (int)testTopic && s.TypeId == testQuestionTypeId && s.Viewed < timeMinusMinutesThatMustPassBeforeAWordIsTestedAgainDateTime
112: orderby s.CumulativeAnswerCorrectnessIndicator, s.NumberOfTimesAsked, s.NumberOfConsecutiveCorrects, s.Id, s.UserId ascending
113: select s).Take(5).FirstOrDefault();
114:
115: if (testQuestion == null)
116: {
117: testQuestion = (from s in db.Scores
118: where s.TestId == (int)testTopic && s.TypeId == testQuestionTypeId
119: orderby s.CumulativeAnswerCorrectnessIndicator, s.NumberOfTimesAsked, s.NumberOfConsecutiveCorrects, s.Id, s.UserId ascending
120: select s).Take(5).FirstOrDefault();
121: }
122: }
123: else
124: {
125: throw new Exception("Unrecognized test option");
126: }
127:
128: if (testQuestion != null)
129: {
130: testQuestionId = testQuestion.Id;
131: testQuestionTypeId = testQuestion.TypeId;
132:
133: // below: update Viewed
134: testQuestion.Viewed = DateTime.UtcNow.AddHours(3);
135: db.SaveChanges();
136:
137: questionList = (from s in db.Scores where s.TestId == (int)testTopic && s.TypeId == testQuestionTypeId && s.Id != testQuestionId select s).ToList();
138:
139: if (questionList.Count > 0)
140: {
141: // below: select random, non-similar numbers
142: while (optionIndex1 == optionIndex2 || optionIndex1 == optionIndex3 || optionIndex2 == optionIndex3 || questionList[optionIndex1].Answer == testQuestion.Answer || questionList[optionIndex2].Answer == testQuestion.Answer || questionList[optionIndex3].Answer == testQuestion.Answer)
143: {
144: optionIndex1 = random.Next(questionList.Count);
145: optionIndex2 = random.Next(questionList.Count);
146: optionIndex3 = random.Next(questionList.Count);
147: }
148:
149: optionList.Add(questionList[optionIndex1]);
150: optionList.Add(questionList[optionIndex2]);
151: optionList.Add(questionList[optionIndex3]);
152:
153: // below: insert the correct answer randomally
154: optionList.Insert(random.Next(optionList.Count + 1), testQuestion);
155: }
156: else
157: {
158: throw new Exception("questionList.Count == 0");
159: }
160: }
161: else
162: {
163: throw new Exception("testQuestion == null");
164: }
165: }
166: else
167: {
168: throw new Exception("Unrecognized test option");
169: }
170: }
171: }
172:
173: ////////////////////////////////////////////////////////////////////////////
174:
175: /// <summary>
176: ///
177: /// </summary>
178: public Ia.TentPlay.Cl.Model.Memorise.Test.AnswerType CheckSelectedAnswerToQuestion(Ia.TentPlay.Cl.Model.Memorise.Score testQuestion, Ia.TentPlay.Cl.Model.Memorise.Score selectedOption, TimeSpan responseTimeSpan, out string debug)
179: {
180: Ia.TentPlay.Cl.Model.Memorise.Test.AnswerType answerType;
181:
182: using (var db = new Ia.TentPlay.Db())
183: {
184: testQuestion = db.Scores.FirstOrDefault(q => q.Id == testQuestion.Id);
185:
186: debug = "timeSpan: " + responseTimeSpan.TotalSeconds + "; cu: " + testQuestion.CumulativeAnswerCorrectnessIndicator + "; num ask: " + testQuestion.NumberOfTimesAsked + "; num con cu: " + testQuestion.NumberOfConsecutiveCorrects + "; av cu:" + string.Format("{0:0.00}", AverageCumulativeAnswerCorrectnessIndicator());
187:
188: if (testQuestion.Answer == selectedOption.Answer)
189: {
190: if (responseTimeSpan < ResponseSecondsThatIndicateMemorizationTimeSpan)
191: {
192: testQuestion.CumulativeAnswerCorrectnessIndicator++;
193: testQuestion.NumberOfConsecutiveCorrects++;
194:
195: answerType = Ia.TentPlay.Cl.Model.Memorise.Test.AnswerType.Correct;
196: }
197: else
198: {
199: answerType = Ia.TentPlay.Cl.Model.Memorise.Test.AnswerType.CorrectButSlow;
200: }
201: }
202: else
203: {
204: testQuestion.CumulativeAnswerCorrectnessIndicator--;
205: testQuestion.NumberOfConsecutiveCorrects = 0;
206:
207: answerType = Ia.TentPlay.Cl.Model.Memorise.Test.AnswerType.Incorrect;
208: }
209:
210: testQuestion.Updated = testQuestion.Viewed = DateTime.UtcNow.AddHours(3);
211: testQuestion.NumberOfTimesAsked++;
212:
213: db.SaveChanges();
214: }
215:
216: return answerType;
217: }
218:
219: ////////////////////////////////////////////////////////////////////////////
220:
221: /// <summary>
222: ///
223: /// </summary>
224: public double AverageCumulativeAnswerCorrectnessIndicator()
225: {
226: double average;
227:
228: average = 0;
229:
230: using (var db = new Ia.TentPlay.Db())
231: {
232: average = (from s in db.Scores where s.TestId == (int)testTopic select s.CumulativeAnswerCorrectnessIndicator).Average();
233: }
234:
235: return average;
236: }
237:
238: ////////////////////////////////////////////////////////////////////////////
239:
240: /// <summary>
241: ///
242: /// </summary>
243: public abstract void PopulateTestDatabaseTableWithInitialQuestionsIfEmpty(Guid userId);
244:
245: ////////////////////////////////////////////////////////////////////////////
246:
247: /// <summary>
248: ///
249: /// </summary>
250: public abstract int WeightedRandomTypeIdAccordingTypeDistribution(Guid userId);
251:
252: ////////////////////////////////////////////////////////////////////////////
253:
254: /// <summary>
255: ///
256: /// </summary>
257: public void DeleteTestRecordsOfUser(Guid userId)
258: {
259: using (var db = new Ia.TentPlay.Db())
260: {
261: //db.Database.ExecuteSqlCommand("delete from Scores where userId = {0}", userId);
262: }
263: }
264:
265: ////////////////////////////////////////////////////////////////////////////
266: ////////////////////////////////////////////////////////////////////////////
267: }
268:
269: ////////////////////////////////////////////////////////////////////////////
270: ////////////////////////////////////////////////////////////////////////////
271: }