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