forked from shanselman/babysmash
-
Notifications
You must be signed in to change notification settings - Fork 1
/
WordFinder.cs
176 lines (152 loc) · 8.04 KB
/
WordFinder.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
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace BabySmash
{
public class WordFinder
{
private const int MinimumWordLength = 2, MaximumWordLength = 15;
private bool wordsReady;
private HashSet<string> words = new HashSet<string>();
public WordFinder(string wordsFilePath)
{
// File path provided should be relative to our running location, so combine for full path safety.
string dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
wordsFilePath = Path.Combine(dir, wordsFilePath);
// Bail if the source word file is not found.
if (!File.Exists(wordsFilePath))
{
// Source word file was not found; place a 'words.txt' file next to BabySmash.exe to enable combining
// letters into typed words. Some common names may work too (but successful OS speech synth may vary).
return;
}
// Load up the string dictionary in the background.
Thread t = new Thread(() =>
{
// Read through the word file and create a hashtable entry for each one with some
// further parsed word data (such as various game scores, etc)
StreamReader sr = new StreamReader(wordsFilePath);
string s = sr.ReadLine();
while (s != null)
{
// Ignore invalid lines, comment lines, or words which are too short or too long.
if (!s.Contains(";") && !s.Contains("/") && !s.Contains("\\") &&
s.Length >= MinimumWordLength && s.Length <= MaximumWordLength)
{
this.words.Add(s.ToUpper());
}
s = sr.ReadLine();
}
// Store all words into separate buckets based on the last letter for faster compares.
// Mark that we're done loading so we can speak words instead of just letters.
wordsReady = true;
});
t.IsBackground = true;
t.Start();
}
public string LastWord(List<UserControl> figuresQueue)
{
// If not done loading, or could not yet form a word based on queue length, just abort.
int figuresPos = figuresQueue.Count - 1;
if (!this.wordsReady || figuresPos < MinimumWordLength - 1)
{
return null;
}
// Loop while the most recently pressed things are still letters; loop proceeds from the
// most recent letter, back towards the beginning, as we only care about the longest word
// that we JUST now finished typing.
string longestWord = null;
var stringToCheck = new StringBuilder();
int lowestIndexToCheck = Math.Max(0, figuresPos - MaximumWordLength);
while (figuresPos >= lowestIndexToCheck)
{
var lastFigure = figuresQueue[figuresPos] as CoolLetter;
if (lastFigure == null)
{
// If we encounter a non-letter, move on with the best word so far (if any).
// IE typing "o [bracket] p e n" can match word "pen" but not "open" since our
// intention back at [bracket] shows we don't necessarily mean to type "open".
break;
}
// Build up the string and check to see if it is a word so far.
stringToCheck.Insert(0, lastFigure.Character);
string s = stringToCheck.ToString();
if (this.words.Contains(stringToCheck.ToString()) && s.Length >= MinimumWordLength)
{
// Since we're progressively checking longer and longer letter combinations,
// each time we find a word, it is our new "longest" word so far.
longestWord = s;
}
figuresPos--;
}
return longestWord;
}
public void AnimateLettersIntoWord(List<UserControl> figuresQueue, string lastWord)
{
// Prepare to animate the letters into their respective positions, on each screen.
Duration duration = new Duration(TimeSpan.FromMilliseconds(1200));
int totalLetters = lastWord.Length;
Point wordCenter = this.FindWordCenter(figuresQueue, totalLetters);
Point wordSize = this.FindWordSize(figuresQueue, totalLetters);
double wordLeftEdge = wordCenter.X - wordSize.X / 2f;
// Figure out where to move each letter used in the word; find the letters used based on
// the word length; they are the last several figures in the figures queue.
for (int i = figuresQueue.Count - 1; i >= figuresQueue.Count - totalLetters; i--)
{
UserControl currentFigure = figuresQueue[i];
// Find the translation animation of this element, or make one if there is not one yet.
var transformGroup = currentFigure.RenderTransform as TransformGroup;
var transform = FindOrAddTranslationTransform(transformGroup);
// We know where we want to center the word, and the word's left edge based on figure
// sizes, and now just need to figure out how far from that left edge we need to adjust
// to make this letter move to the correct relative position to spell out the word.
double wordOffsetX = 0d;
for (int j = figuresQueue.Count - totalLetters; j < i; j++)
{
wordOffsetX += figuresQueue[j].Width;
}
// Start translating from wherever we were already translated to (or 0 if not yet
// translated) and going to the new position for this letter based for the word.
var wordTranslationX = wordLeftEdge - Canvas.GetLeft(currentFigure);
var wordTranslationY = wordCenter.Y - Canvas.GetTop(currentFigure);
var animationX = new DoubleAnimation(transform.X, wordTranslationX + wordOffsetX, duration);
var animationY = new DoubleAnimation(transform.Y, wordTranslationY, duration);
transform.BeginAnimation(TranslateTransform.XProperty, animationX);
transform.BeginAnimation(TranslateTransform.YProperty, animationY);
}
}
private Point FindWordCenter(List<UserControl> letterQueue, int letterCount)
{
// For now, target centering the word at the average position of all its letters.
var x = (from c in letterQueue select Canvas.GetLeft(c)).Reverse().Take(letterCount).Average();
var y = (from c in letterQueue select Canvas.GetTop(c)).Reverse().Take(letterCount).Average();
return new Point(x, y);
}
private Point FindWordSize(List<UserControl> letterQueue, int letterCount)
{
var x = (from c in letterQueue select c.Width).Reverse().Take(letterCount).Sum();
var y = (from c in letterQueue select c.Height).Reverse().Take(letterCount).Max();
return new Point(x, y);
}
private TranslateTransform FindOrAddTranslationTransform(TransformGroup transformGroup)
{
var translationTransform = (from t in transformGroup.Children
where t is TranslateTransform
select t).FirstOrDefault() as TranslateTransform;
if (translationTransform == null)
{
translationTransform = new TranslateTransform();
transformGroup.Children.Add(translationTransform);
}
return translationTransform;
}
}
}