-
Notifications
You must be signed in to change notification settings - Fork 0
/
CTeacher.cs
306 lines (264 loc) · 8.26 KB
/
CTeacher.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
using System;
using System.Collections;
using System.Windows.Forms;
namespace NFingers
{
public class CTeacher
{
public delegate void OnBadAnswerDelegate();
public delegate void OnGoodAnswerDelegate();
public delegate void OnNewLineDelegate(int _iCurLine, int _iTotalLines);
public delegate void OnNewLevelDelegate(string _strLevelInfo);
public event OnBadAnswerDelegate OnBadAnswer;
public event OnGoodAnswerDelegate OnGoodAnswer;
public event OnNewLineDelegate OnNewLine;
public event OnNewLevelDelegate OnNewLevel;
private SConfig m_cfg; // copy of configuration from `FFingers`
private ArrayList m_al; // array of level-lines
private string m_strLevelInfo;
private string m_strCurLine; // current task line (CTL)
private int m_iCurLine; // position index of CTL in `m_al`
private int m_iCurChar; // current char in CTL
// speed: Char Per Minute. evaluated to ch/min number of pressed (good or bad) chars
// measured each `ETeacherSay.NEXT_LINE` event
private double m_dCpm; // cpm per task-line
private double m_dCpmAverage; // average cpm per level
private double m_dEpmPercent;
private double m_dEpmPercentAverage;
private int m_iTyped; // number of typed chars perline
private int m_iMissTyped; // number of misstyped chars perline
// time stamp when current task-line completion is started
private TimeSpan m_tsLineStart;
// time stamp when current task-line is complate
private TimeSpan m_tsLineDone;
// ratio of misstyped chars to totaly typed per line
private bool m_bPaused = false;
private TimeSpan m_tsPauseMoment;
/////////////////////////////////////////////////////////////////////////
public SConfig Config
{
get { return m_cfg; }
set { m_cfg = value; }
}
public string LevelInfo
{
get { return m_strLevelInfo; }
// Level info it's a first (commonly commented with '#' char) line in
// level definition file; this method sets this line whithout
// a comment and ' '.
set
{
if (value[0] == '#')
{// skip leading '#' recursively
LevelInfo = value.Substring(1);
}
else if (value[0] == ' ')
{// skip ' ' after '#'
m_strLevelInfo = value.Substring(1);
}
else
{
m_strLevelInfo = value;
}
}
}
/// <summary>
/// Level is started if no key was processed
/// </summary>
public bool IsLevelStarted
{
get
{
return m_iCurLine > 1 || (m_iCurLine == 1 && m_iCurChar > 0);
}
}
public bool PauseMode
{
get
{
return m_bPaused;
}
set
{
if (value)
{
if (!m_bPaused)
{
m_bPaused = true;
m_tsPauseMoment = DateTime.Now.TimeOfDay;
}
}
else
{
if (m_bPaused)
{
m_bPaused = false;
m_tsLineStart = DateTime.Now.TimeOfDay - (m_tsPauseMoment - m_tsLineStart);
}
}
}
}
/////////////////////////////////////////////////////////////////////////
private CTeacher() { }
public CTeacher(SConfig _cfg)
{
m_cfg = _cfg;
Init();
}
public void Init()
{
m_al = CLevels.GetLevel(m_cfg.LastLevel, m_cfg.Filename);
m_iCurLine = 1;
m_iCurChar = 0;
LevelInfo = (m_al[0] as string);
m_strCurLine = (m_al[1] as string);
if (OnNewLevel != null)
{
OnNewLevel(LevelInfo);
}
m_iTyped = 0;
m_iMissTyped = 0;
m_dEpmPercent = 0.0;
m_dEpmPercentAverage = 0.0;
m_dCpm = 0.0;
m_dCpmAverage = 0.0;
m_tsLineStart = new TimeSpan(0);
m_tsLineDone = new TimeSpan(0);
RefreshStatistic();
}
/// <summary>Statistic refreshement done at every `OnNextLine` event.</summary>
private void RefreshStatistic()
{
if (0 == m_iTyped) { return; }
TimeSpan tsMin = new TimeSpan(0, 1, 0); // 1 minute etalon
m_tsLineDone = DateTime.Now.TimeOfDay - m_tsLineStart;
// ratio of measured time regarding to 1 minute etalon
double dRatio = m_tsLineDone.TotalSeconds / tsMin.TotalSeconds;
/** number of characters typed per line is evaluated to
* number of characters typed per minute */
--m_iTyped;
m_dCpm = ((dRatio != 0)? (m_iTyped / dRatio) : 0);
m_dEpmPercent = ((m_iTyped != 0)? (100.0 * (double) m_iMissTyped / (double) m_iTyped) : 0);
m_dCpmAverage = (m_dCpmAverage * (m_iCurLine - 1) + m_dCpm) / m_iCurLine;
m_cfg.CharPermin = Convert.ToInt32(m_dCpmAverage);
m_dEpmPercentAverage = ((m_dEpmPercentAverage * (m_iCurLine - 1) + m_dEpmPercent) / m_iCurLine);
m_cfg.ErrPermin = (int) m_dEpmPercentAverage;
m_iTyped = 0;
m_iMissTyped = 0;
}
public void Check(char _chUserHit)
{
++m_iTyped;
if (0 == m_iCurChar)
{
// time stamp of the current-line-execution-started taken
// only at the moment when first char of it has been pressed
m_tsLineStart = DateTime.Now.TimeOfDay;
}
// check the answer
if (m_strCurLine[m_iCurChar] == _chUserHit)
{
// right answer
if (m_iCurChar < (m_strCurLine.Length - 1))
{
// set next char in the current task-line
++m_iCurChar;
if (OnGoodAnswer != null)
{
OnGoodAnswer();
}
}
else
{
// jump to next line of the current task
RefreshStatistic();
++m_iCurLine;
m_iCurChar = 0;
if (OnGoodAnswer != null)
{
OnGoodAnswer();
}
OnNewLine(m_iCurLine - 1, m_al.Count - 1);
}
}
else
{
// wrong answer
++m_iMissTyped;
if (OnBadAnswer != null)
{
OnBadAnswer();
}
}
}
public void RestartLevel()
{
Init();
}
public void SetLevelTo(int _iLevel)
{
m_cfg.LastLevel = _iLevel;
Init();
}
public string RefreshTaskForward(out char _chOnTarget)
{
if (m_iCurLine > (m_al.Count - 1))
{
// current level is ended, look around about new level
if ((m_dCpmAverage > m_cfg.LevelSpeedUp)
&& (m_dEpmPercentAverage < m_cfg.LevelErrorDown))
{
// typing-speed and error-precentage allow to encrease level up
if ( (m_cfg.LastLevel != CLevels.LevelsCount)
&& (DialogResult.OK == MessageBox.Show(
"Your typing skills allow to advance to the next level."
+"\nIncreasing level?"
, "Level is complete",
MessageBoxButtons.OKCancel, MessageBoxIcon.Information)) )
{
++m_cfg.LastLevel;
}
}
else if ((m_dCpmAverage < m_cfg.LevelSpeedDown)
|| (m_dEpmPercentAverage > m_cfg.LevelErrorDown))
{
// typning-speed or error percentage are too low - so decrease the level
if ( (m_cfg.LastLevel != 1)
&& (DialogResult.OK == MessageBox.Show(
"Your typing skills are too low for this level."
+ "\nDecreasing level?"
, "Level is complete",
MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation)) )
{
--m_cfg.LastLevel;
}
}
else
{
// else if error percentage and typing speed are still in bounds
// so stay at current level
MessageBox.Show("Your typing skills are good however that is not enought to advance to the next level."
+"\nKeep practicing."
, "Level is complete"
, MessageBoxButtons.OK, MessageBoxIcon.Information);
}
Init();
}
m_strCurLine = m_al[m_iCurLine] as string;
string strRet = m_strCurLine.Substring(m_iCurChar);
_chOnTarget = strRet[0];
return strRet;
}
public string RefreshTaskBackward(out char _chOnTarget)
{
string strRet;
if (m_iCurChar >= 1)
{// have leading chars
--m_iCurChar;
}
strRet = m_strCurLine.Substring(m_iCurChar);
_chOnTarget = strRet[0];
return strRet;
}
};
}