klavaro  3.13
About: Klavaro is a touch typing tutor program.
  Fossies Dox: klavaro-3.13.tar.bz2  ("unofficial" and yet experimental doxygen-generated source code documentation)  

accuracy.c
Go to the documentation of this file.
1 /**************************************************************************/
2 /* Klavaro - a flexible touch typing tutor */
3 /* Copyright (C) 2005-2021 Felipe Emmanuel Ferreira de Castro */
4 /* */
5 /* This file is part of Klavaro, which is a free software: you can */
6 /* redistribute it and/or modify it under the terms of the GNU General */
7 /* Public License as published by the Free Software Foundation, either */
8 /* version 3 of the License, or (at your option) any later version. */
9 /* */
10 /* Klavaro is distributed in the hope that it will be useful, */
11 /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
12 /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
13 /* GNU General Public License for more details (in the file COPYING). */
14 /* You should have received a copy of the GNU General Public License */
15 /* along with Klavaro. If not, see <https://www.gnu.org/licenses/> */
16 /**************************************************************************/
17 
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <math.h>
21 #include <string.h>
22 #include <glib.h>
23 #include <glib/gstdio.h>
24 
25 #include "main.h"
26 #include "auxiliar.h"
27 #include "keyboard.h"
28 #include "adaptability.h"
29 #include "accuracy.h"
30 #include "plot.h"
31 
32 /* Touch errors
33  */
34 struct
35 {
36  gunichar uchr;
37  gulong wrong;
38  gulong correct;
40 gint terror_n = 0;
41 
42 /* Touch times
43  */
44 struct
45 {
46  gunichar uchr;
47  gdouble dt[MAX_TT_SAVED + 2];
48  gint idx;
50 gint ttime_n = 0;
51 
52 /* Simple reset
53  */
54 void
56 {
57  memset (&terror, 0, sizeof (terror));
58  terror_n = 0;
59 }
60 
61 void
63 {
64  memset (&ttime, 0, sizeof (ttime));
65  ttime_n = 0;
66 }
67 
68 void
70 {
73 }
74 
75 gint
77 {
78  return (terror_n);
79 }
80 
81 gint
83 {
84  return (ttime_n);
85 }
86 
87 gchar *
89 {
90  gchar *utf8;
91  gint n;
92 
93  if (i < 0 || i >= terror_n)
94  return (g_strdup (" "));
95 
96  utf8 = g_malloc (UTF8_BUFFER);
97  n = g_unichar_to_utf8 (terror[i].uchr, utf8);
98  if (n < 1)
99  return (g_strdup (" "));
100  utf8[n] = '\0';
101 
102  return (utf8);
103 }
104 
105 gchar *
107 {
108  gchar *utf8;
109  gint n;
110 
111  if (i < 0 || i >= ttime_n)
112  return (g_strdup (" "));
113 
114  utf8 = g_malloc (UTF8_BUFFER);
115  n = g_unichar_to_utf8 (ttime[i].uchr, utf8);
116  if (n < 1)
117  return (g_strdup (" "));
118  utf8[n] = '\0';
119 
120  return (utf8);
121 }
122 
123 gulong
125 {
126  if (i < 0 || i >= terror_n)
127  return -1;
128 
129  return (terror[i].wrong);
130 }
131 
132 void
134 {
135  gint i, j;
136 
137  g_printf ("\n");
138  for (i = 0; i < ttime_n; i++)
139  {
140  g_printf ("%2i - %C - (%02i)", i, ttime[i].uchr, ttime[i].idx);
141  for (j = 0; j < ttime[i].idx; j+=1)
142  g_printf (" %2i - %.2lf", j, ttime[i].dt[j]);
143  g_printf ("\n");
144  }
145 }
146 
147 /**********************************************************************
148  * Open the accuracy accumulators
149  */
150 void
152 {
153  gint i, j;
154  gchar *tmp;
155  gchar tmpchr[UTF8_BUFFER];
156  gchar *kb_name;
157  gchar *data;
158  gchar *dtp;
159  gint nok;
160  gboolean success;
161  gunichar uchr;
162  gulong wrong;
163  gulong correct;
164  gdouble dt, dummy;
165  gsize len;
166 
167  accur_reset ();
168 
169  kb_name = g_strdup (keyb_get_name ());
170  for (i=0; kb_name[i]; i++)
171  kb_name[i] = (kb_name[i] == ' ') ? '_' : kb_name[i];
172 
173  /*
174  * First, the accuracy log
175  */
176  tmp = g_strconcat (main_path_stats (), G_DIR_SEPARATOR_S, ACCUR_LOG_FILE, "_", kb_name, NULL);
177  success = g_file_get_contents (tmp, &data, &len, NULL);
178  if (!success)
179  g_message ("Empty accuracy log: %s", tmp);
180  else if (len > 0)
181  {
182  dtp = data;
183  for (i = 0; i < MAX_CHARS_EVALUATED; i++)
184  {
185  if (3 != sscanf (dtp, "%6s\t%lu\t%lu\n", tmpchr, &wrong, &correct))
186  {
187  g_warning ("Accuracy file input error!");
188  break;
189  }
190  uchr = g_utf8_get_char_validated (tmpchr, -1);
191  if (uchr == (gunichar)-1 || uchr == (gunichar)-2)
192  {
193  g_warning ("Accuracy file UTF-8 input error!");
194  break;
195  }
196 
197  if (1234 > wrong && wrong > 0 && correct <= ERROR_INERTIA)
198  {
199  terror[i].uchr = uchr;
200  terror[i].wrong = wrong;
201  terror[i].correct = correct;
202  terror_n = i + 1;
203  }
204  else
205  break;
206  while (dtp[0] != '\0' && dtp[0] != '\n') dtp++;
207  if (dtp[0] == '\n') dtp++;
208  if (dtp[0] == '\0') break;
209  }
210  g_free (data);
211  }
212  g_free (tmp);
213 
214  /*
215  * Second, the proficiency log
216  */
217  tmp = g_strconcat (main_path_stats (), G_DIR_SEPARATOR_S, PROFI_LOG_FILE, "_", kb_name, NULL);
218  success = g_file_get_contents (tmp, &data, &len, NULL);
219  if (!success)
220  g_message ("Empty proficiency log: %s", tmp);
221  else if (len > 0)
222  {
223  dtp = data;
224  for (i = 0; i < MAX_CHARS_EVALUATED; i++)
225  {
226  if (3 != sscanf (dtp, "%6s\t%lf\t%lf\n", tmpchr, &dt, &dummy))
227  {
228  g_warning ("Proficiency file input error!");
229  break;
230  }
231 
232  uchr = g_utf8_get_char_validated (tmpchr, -1);
233  if (uchr == (gunichar)-1 || uchr == (gunichar)-2)
234  {
235  g_warning ("Proficiency file UTF-8 input error!");
236  break;
237  }
238 
239  if (dt > 0)
240  {
241  ttime[i].uchr = uchr;
242  for (j = 0; j < 3; j++)
243  ttime[i].dt[j] = dt;
244  ttime[i].idx = 3;
245  ttime_n = i + 1;
246  }
247  else
248  break;
249 
250  while (dtp[0] != '\0' && dtp[0] != '\n') dtp++;
251  if (dtp[0] == '\n') dtp++;
252  if (dtp[0] == '\0') break;
253  }
254  g_free (data);
255  }
256  g_free (tmp);
257  g_free (kb_name);
258  /* accur_ttime_print (); */
259 }
260 
261 /**********************************************************************
262  * Accumulates correctly typed characters
263  */
264 void
265 accur_correct (gunichar uchr, double touch_time)
266 {
267  gint i, j;
268 
269  if (uchr == L' ' || uchr == UPSYM || uchr == L'\t' || uchr == L'\b')
270  return;
271  if (!keyb_is_inset (uchr))
272  return;
273 
274  /*
275  * First, accuracy
276  */
277  for (i = 0; i < terror_n; i++)
278  {
279  if (uchr == terror[i].uchr)
280  {
281  if (terror[i].correct > ERROR_INERTIA)
282  {
283  terror[i].wrong -= (terror[i].wrong == 0 ? 0 : 1);
284  terror[i].correct = 1;
285  }
286  else
287  terror[i].correct++;
288  break;
289  }
290  }
291 
292  /*
293  * Second, proficiency
294  */
295  if (touch_time < 0.001)
296  return;
297 
298  /* If uchar is in the pool, add the touch time to it and return */
299  uchr = g_unichar_tolower (uchr);
300  for (i = 0; i < ttime_n; i++)
301  {
302  if (uchr == ttime[i].uchr)
303  {
304  ttime[i].dt[ttime[i].idx] = touch_time;
305  ttime[i].idx++;
306  if (ttime[i].idx >= (MAX_TT_SAVED - 1))
307  ttime[i].idx = 0;
308  return;
309  }
310  }
311 
312  /* else, if there is room, */
314  return;
315 
316  /* include a new key in the pool */
317  ttime[i].uchr = uchr;
318  ttime[i].dt[0] = touch_time;
319  for (j = 1; j < MAX_TT_SAVED; j++)
320  ttime[i].dt[j] = 0.0;
321  ttime[i].idx = 1;
322  ttime_n++;
323 }
324 
325 /**********************************************************************
326  * Accumulates mistyped characters
327  */
328 void
329 accur_wrong (gunichar uchr)
330 {
331  gint i;
332  gint i_min = -1;
333  gdouble correct_min = 1e9;
334  gdouble hlp;
335 
336  if (uchr == L' ' || uchr == UPSYM || uchr == L'\t' || uchr == L'\b')
337  return;
338  if (!keyb_is_inset (uchr))
339  return;
340 
341  /*
342  * Only for accuracy
343  */
344  for (i = 0; i < terror_n; i++)
345  {
346  if (uchr == terror[i].uchr)
347  {
348  terror[i].wrong++;
349  return;
350  }
351  hlp = ((gdouble) terror[i].wrong) / ((gdouble) terror[i].correct + terror[i].wrong + 1);
352  if (hlp <= correct_min)
353  {
354  correct_min = hlp;
355  i_min = i;
356  }
357  }
358 
360  {
361  i = terror_n;
362  terror_n++;
363  }
364  else
365  {
366  i = (i_min > -1 ? i_min : terror_n - 1);
367  }
368 
369  terror[i].uchr = uchr;
370  terror[i].wrong = 1;
371  terror[i].correct = 1;
372 }
373 
374 gulong
376 {
377  gint i;
378  gulong n = 0;
379 
380  for (i = 0; i < terror_n; i++)
381  n += (terror[i].wrong < 12345 ? terror[i].wrong : 0);
382 
383  return n;
384 }
385 
386 gdouble
388 {
389  gint i, n;
390  gdouble sum;
391 
392  if (idx < 0)
393  return -10;
394 
395  /* Simple average */
396  n = 0; sum = 0;
397  for (i = 0; i < MAX_TT_SAVED; i++)
398  {
399  if (ttime[idx].dt[i] > 0.0 && ttime[idx].dt[i] < 3.0)
400  {
401  sum += ttime[idx].dt[i];
402  n++;
403  }
404  }
405  if (n == 0)
406  return 0.0;
407 
408  return (sum / n);
409 }
410 
411 gint
413 {
414  gint norm;
415 
416  if (idx < 0)
417  return -1;
418  if (accur_profi_aver (ttime_n-1) == 0)
419  return -3;
420 
421  norm = rint (accur_profi_aver (idx) / accur_profi_aver (ttime_n-1));
422 
423  return norm;
424 }
425 
426 /*******************************************************************************
427  * Sorting first: decreasing wrongness; second: increasing correctness
428  */
429 void
431 {
432  gint i, j;
433  gunichar uchr;
434  gulong correct;
435  gulong wrong;
436 
437  for (i = 1; i < terror_n; i++)
438  {
439  for (j = i; j > 0; j--)
440  {
441  if (terror[j].correct < terror[j-1].correct)
442  {
443  uchr = terror[j].uchr;
444  terror[j].uchr = terror[j-1].uchr;
445  terror[j-1].uchr = uchr;
446 
447  wrong = terror[j].wrong;
448  terror[j].wrong = terror[j-1].wrong;
449  terror[j-1].wrong = wrong;
450 
451  correct = terror[j].correct;
452  terror[j].correct = terror[j-1].correct;
453  terror[j-1].correct = correct;
454  }
455  }
456  }
457  for (i = 1; i < terror_n; i++)
458  {
459  for (j = i; j > 0; j--)
460  {
461  if (terror[j].wrong > terror[j-1].wrong)
462  {
463  uchr = terror[j].uchr;
464  terror[j].uchr = terror[j-1].uchr;
465  terror[j-1].uchr = uchr;
466 
467  wrong = terror[j].wrong;
468  terror[j].wrong = terror[j-1].wrong;
469  terror[j-1].wrong = wrong;
470 
471  correct = terror[j].correct;
472  terror[j].correct = terror[j-1].correct;
473  terror[j-1].correct = correct;
474  }
475  }
476  }
477 }
478 
479 /*******************************************************************************
480  * Decreasing order, touch time
481  */
482 void
484 {
485  gint i, j, k;
486  gunichar uchr;
487  gdouble dt;
488  gint idx;
489 
490  for (i = 1; i < ttime_n; i++)
491  {
492  for (j = i; j > 0; j--)
493  {
494  if (accur_profi_aver (j) > accur_profi_aver (j-1))
495  {
496  uchr = ttime[j].uchr;
497  ttime[j].uchr = ttime[j-1].uchr;
498  ttime[j-1].uchr = uchr;
499 
500  idx = ttime[j].idx;
501  ttime[j].idx = ttime[j-1].idx;
502  ttime[j-1].idx = idx;
503 
504  for (k = 0; k < MAX_TT_SAVED; k++)
505  {
506  if (ttime[j].dt[k] == ttime[j-1].dt[k])
507  continue;
508  dt = ttime[j].dt[k];
509  ttime[j].dt[k] = ttime[j-1].dt[k];
510  ttime[j-1].dt[k] = dt;
511  }
512  }
513  }
514  }
515 
516  for (i = ttime_n - 1; i > -1; i--)
517  {
518  if (accur_profi_aver (i) < 0.001)
519  {
520  ttime[i].uchr = 0;
521  for (j = 0; j < MAX_TT_SAVED; j++)
522  ttime[i].dt[j] = 0;
523  ttime[i].idx = 0;
524  ttime_n--;
525  }
526  else
527  break;
528  }
529  /* accur_ttime_print (); */
530 }
531 
532 void
534 {
536  accur_ttime_sort ();
537 }
538 
539 /*******************************************************************************
540  * Creates a random weird word based on error profile
541  */
542 gboolean
543 accur_create_word (gunichar word[MAX_WORD_LEN + 1])
544 {
545  gint i, j;
546  gint ind;
547  gint n;
548  gunichar vowels[20];
549  gunichar last = 0;
550  gint vlen;
551  gboolean ptype_terror;
552  gboolean ptype_ttime;
553  static gint profile_type = 0;
554 
555  if (terror_n < 10 && ttime_n < 5)
556  return FALSE;
557 
558  ptype_terror = accur_error_total () >= ERROR_LIMIT;
559  if (ttime_n < 40)
560  ptype_ttime = 0;
561  else
562  ptype_ttime = ((accur_profi_aver(9)/accur_profi_aver(39)) > PROFI_LIMIT);
563  vlen = keyb_get_vowels (vowels);
564  n = rand () % (MAX_WORD_LEN) + 1;
565  for (i = 0; i < n; i++)
566  {
567  if (profile_type == 0)
568  profile_type = ptype_ttime ? 1 : 0;
569  else
570  profile_type = ptype_terror ? 0 : 1;
571 
572  if (profile_type == 0) /* Error */
573  {
574  for (j = 0; j < 100; j++)
575  {
576  ind = rand () % terror_n;
577  if (terror[ind].uchr == last)
578  continue;
579  if (rand () % terror[0].wrong < terror[ind].wrong)
580  break;
581  }
582  word[i] = terror[ind].uchr;
583  }
584  else /* Time */
585  {
586  for (j = 0; j < 100; j++)
587  {
588  ind = rand () % ttime_n;
589  if (ttime[ind].uchr == last)
590  continue;
591  if ((rand () % accur_profi_aver_norm (0)) < accur_profi_aver_norm (ind))
592  break;
593  }
594  word[i] = ttime[ind].uchr;
595  }
596  last = word[i];
597 
598  /* Avoid double diacritics
599  */
600  if (i > 0)
601  if (keyb_is_diacritic (word[i - 1]) && keyb_is_diacritic (word[i]))
602  {
603  word[i] = vowels[rand () % vlen];
604  last = word[i];
605  }
606  }
607  /*
608  * Null terminated unistring
609  */
610  word[n] = L'\0';
611 
612  return TRUE;
613 }
614 
615 /*******************************************************************************
616  * Saves the accuracy accumulator
617  */
618 void
620 {
621  gint i;
622  gchar *kb_name;
623  gchar *tmp;
624  gchar *utf8;
625  FILE *fh;
626 
627  accur_sort ();
628 
629  kb_name = g_strdup (keyb_get_name ());
630  for (i=0; kb_name[i]; i++)
631  kb_name[i] = (kb_name[i] == ' ') ? '_' : kb_name[i];
632 
633  /*
634  * First, the accuracy log
635  */
636  tmp = g_strconcat (main_path_stats (), G_DIR_SEPARATOR_S, ACCUR_LOG_FILE, "_", kb_name, NULL);
637  fh = g_fopen (tmp, "wb");
638  g_free (tmp);
639  if (fh)
640  {
641  for (i = 0; i < terror_n; i++)
642  {
643  if (terror[i].wrong == 0 || terror[i].correct > ERROR_INERTIA)
644  continue;
645 
646  utf8 = accur_terror_char_utf8 (i);
647  g_fprintf (fh, "%s\t%2lu\t%2lu\n", utf8, terror[i].wrong, terror[i].correct);
648  g_free (utf8);
649  }
650  fclose (fh);
651  }
652  else
653  g_message ("Could not save an accuracy log file at %s", main_path_stats ());
654 
655  /*
656  * Second, the proficiency log
657  */
658  tmp = g_strconcat (main_path_stats (), G_DIR_SEPARATOR_S, PROFI_LOG_FILE, "_", kb_name, NULL);
659  fh = g_fopen (tmp, "wb");
660  g_free (tmp);
661  if (fh)
662  {
663  for (i = 0; i < ttime_n; i++)
664  {
665  utf8 = accur_ttime_char_utf8 (i);
666  g_fprintf (fh, "%s", utf8);
667  //g_printf ("%s", utf8);
668  g_free (utf8);
669  g_fprintf (fh, "\t%0.6f\t%0.4f\n",
672  //g_printf ("\t%0.6f\t%0.4f\n", accur_profi_aver (i), accur_profi_aver (i) / accur_profi_aver (ttime_n-1));
673  }
674  fclose (fh);
675  }
676  else
677  g_message ("Could not save a proficiency log file at %s", main_path_stats ());
678 
679  g_free (kb_name);
680 }
gulong accur_error_total()
Definition: accuracy.c:375
void accur_reset()
Definition: accuracy.c:69
void accur_sort()
Definition: accuracy.c:533
void accur_ttime_sort()
Definition: accuracy.c:483
void accur_close()
Definition: accuracy.c:619
gint idx
Definition: accuracy.c:48
struct @1 ttime[130]
gunichar uchr
Definition: accuracy.c:36
void accur_terror_reset()
Definition: accuracy.c:55
void accur_terror_sort()
Definition: accuracy.c:430
gboolean accur_create_word(gunichar word[9+1])
Definition: accuracy.c:543
gulong accur_wrong_get(gint i)
Definition: accuracy.c:124
void accur_ttime_print(void)
Definition: accuracy.c:133
void accur_ttime_reset()
Definition: accuracy.c:62
gint ttime_n
Definition: accuracy.c:50
void accur_wrong(gunichar uchr)
Definition: accuracy.c:329
gulong correct
Definition: accuracy.c:38
void accur_init()
Definition: accuracy.c:151
gchar * accur_terror_char_utf8(gint i)
Definition: accuracy.c:88
gulong wrong
Definition: accuracy.c:37
void accur_correct(gunichar uchr, double touch_time)
Definition: accuracy.c:265
gint terror_n
Definition: accuracy.c:40
gint accur_ttime_n_get(void)
Definition: accuracy.c:82
gchar * accur_ttime_char_utf8(gint i)
Definition: accuracy.c:106
struct @0 terror[130]
gint accur_terror_n_get(void)
Definition: accuracy.c:76
gdouble dt[30+2]
Definition: accuracy.c:47
gint accur_profi_aver_norm(gint idx)
Definition: accuracy.c:412
gdouble accur_profi_aver(gint idx)
Definition: accuracy.c:387
#define ERROR_INERTIA
Definition: accuracy.h:25
#define PROFI_LOG_FILE
Definition: accuracy.h:22
#define PROFI_LIMIT
Definition: accuracy.h:27
#define UTF8_BUFFER
Definition: accuracy.h:29
#define ACCUR_LOG_FILE
Definition: accuracy.h:21
#define MAX_TT_SAVED
Definition: accuracy.h:24
#define MAX_CHARS_EVALUATED
Definition: accuracy.h:23
#define ERROR_LIMIT
Definition: accuracy.h:26
#define MAX_WORD_LEN
Definition: adaptability.h:20
guint i
Definition: keyboard.c:55
gchar * keyb_get_name()
Definition: keyboard.c:204
guint j
Definition: keyboard.c:56
gboolean keyb_is_inset(gunichar chr)
Definition: keyboard.c:442
gint keyb_get_vowels(gunichar *vows)
Definition: keyboard.c:491
gboolean keyb_is_diacritic(gunichar chr)
Definition: keyboard.c:476
const gunichar vowels[]
Definition: keyboard.c:73
#define UPSYM
Definition: keyboard.h:23
gchar * main_path_stats()
Definition: main.c:67
gchar * data
Definition: main.c:53
gdouble touch_time[4000+1]
Definition: tutor.c:55
gint len
Definition: velocity.c:40