Updated German translation
[dasher.git] / Src / DasherCore / GameModule.cpp
blobb05574849d117684688b596b3ad29be6283a1e38
1 #include "GameModule.h"
2 #include "GameStatistics.h"
3 #include <sstream>
5 using namespace Dasher;
7 static SModuleSettings gameSets[] = {
8 {SP_GAME_TEXT_FILE, T_STRING, -1, -1, -1, -1, _("Filename of sentences to enter")},
9 {LP_GAME_HELP_DIST, T_LONG, 0, 4096, 2048, 128, _("Distance of sentence from center to decide user needs help")},
10 {LP_GAME_HELP_TIME, T_LONG, 0, 10000, 1000, 100, _("Time for which user must need help before help drawn")},
11 {BP_GAME_HELP_DRAW_PATH, T_BOOL, -1, -1, -1, -1, _("When we give help, show the shortest path to the target sentence?")},
14 CGameModule::CGameModule(CSettingsUser *pCreateFrom, Dasher::CDasherInterfaceBase *pInterface, CDasherView *pView, CDasherModel *pModel)
15 : CSettingsUser(pCreateFrom), TransientObserver<const Dasher::CEditEvent *>(pInterface), TransientObserver<CGameNodeDrawEvent*>(pView),
16 TransientObserver<CDasherNode*>(pModel), TransientObserver<CDasherView*>(pView),
17 m_pInterface(pInterface), m_iLastSym(-1),
18 m_y1(std::numeric_limits<myint>::min()), m_y2(std::numeric_limits<myint>::max()),
19 m_iTargetY(CDasherModel::ORIGIN_Y), m_uHelpStart(std::numeric_limits<unsigned long>::max()),
20 m_ulTotalTime(0), m_dTotalNats(0.0), m_uiTotalSyms(0),
21 m_iCrosshairColor(135), m_iFontSize(36)
24 bool CGameModule::GetSettings(SModuleSettings **sets, int *count) {
25 *sets = gameSets;
26 *count = sizeof(gameSets) / sizeof(gameSets[0]);
27 return true;
30 CGameModule::~CGameModule() {
31 if (m_ulTotalTime) {
32 //TODO make this a running commentary?
33 ostringstream summary;
34 summary << "Total time " << m_ulTotalTime;
35 summary << " nats " << m_dTotalNats << "=" << (m_dTotalNats*1000.0/m_ulTotalTime) << "/sec";
36 summary << " chars " << m_uiTotalSyms << "=" << (m_uiTotalSyms/m_ulTotalTime) << "/sec";
37 m_pInterface->Message(summary.str(),true);
39 m_pInterface->ClearAllContext();
42 //Node populated...
43 void CGameModule::HandleEvent(CDasherNode *pNode) {
44 if (pNode->GetFlag(NF_GAME) //if on game path, look for next/child node on path...
45 && pNode->offset()+1 < m_vTargetSymbols.size())
46 pNode->GameSearchChildren(m_vTargetSymbols[pNode->offset()+1]);
49 void CGameModule::HandleEvent(const CEditEvent *evt) {
50 if (!m_pAlph) return; //Game Mode currently not running
51 const int iOffset(evt->m_pNode->offset());
52 switch(evt->m_iEditType) {
53 // Added a new character (Stepped one node forward)
54 case CEditEvent::EDIT_OUTPUT:
55 if (iOffset == m_iLastSym+1
56 && iOffset < m_vTargetSymbols.size()) {
57 DASHER_ASSERT(m_strWrong == "");
58 if (evt->m_sText == m_pAlph->GetText(m_vTargetSymbols[iOffset])) {
59 // User has entered correct text...
60 ++m_iLastSym;
61 } else m_strWrong = evt->m_sText;
62 } else {
63 DASHER_ASSERT(iOffset >= m_iLastSym+1);
64 m_strWrong+=evt->m_sText;
66 break;
67 // Removed a character (Stepped one node back)
68 case CEditEvent::EDIT_DELETE:
69 if (iOffset == m_iLastSym) {
70 //seems they've just deleted the last _correct_ character they'd entered...
71 DASHER_ASSERT(evt->m_sText == m_pAlph->GetText(m_vTargetSymbols[m_iLastSym]));
72 --m_iLastSym;
73 } else {
74 //just deleted previously-entered wrong text - hopefully they're heading in the right direction!
75 DASHER_ASSERT(m_strWrong.length() >= evt->m_sText.length());
76 DASHER_ASSERT(m_strWrong.substr(m_strWrong.length() - evt->m_sText.length()) == evt->m_sText);
77 m_strWrong = m_strWrong.substr(0,m_strWrong.length() - evt->m_sText.length());
79 break;
80 default:
81 break;
85 void CGameModule::HandleEvent(CGameNodeDrawEvent *gmd) {
86 //game nodes form a single chain, i.e. are strictly nested.
87 // we want the coordinates of the smallest (innermost) one about which we are told
88 if (gmd->m_y1 > m_y1) m_y1 = gmd->m_y1;
89 if (gmd->m_y2 < m_y2) m_y2 = gmd->m_y2;
92 void CGameModule::HandleEvent(CDasherView *pView) {
93 if (pView!=TransientObserver<CGameNodeDrawEvent*>::m_pEventHandler) {
94 TransientObserver<CGameNodeDrawEvent*>::m_pEventHandler->Unregister(this);
95 (TransientObserver<CGameNodeDrawEvent*>::m_pEventHandler = pView)->Register(this);
99 void CGameModule::SetWordGenerator(const CAlphInfo *pAlph, CWordGeneratorBase *pWordGenerator) {
100 m_pAlph = pAlph;
101 m_pWordGenerator = pWordGenerator;
102 if (!GenerateChunk()) {
103 m_pInterface->Message("Game mode sentences file empty!",true);
104 //this'll delete the 'this' pointer, so we'd better not do anything else afterwards!...
105 m_pInterface->LeaveGameMode();
109 void CGameModule::StartWriting(unsigned long lTime) {
110 if (!m_ulSentenceStartTime) {
111 m_ulSentenceStartTime = lTime;
112 m_dSentenceStartNats = numeric_limits<double>::max();
116 void CGameModule::DecorateView(unsigned long lTime, CDasherView *pView, CDasherModel *pModel) {
118 if (m_dSentenceStartNats == numeric_limits<double>::max())
119 m_dSentenceStartNats = pModel->GetNats();
121 const myint iNewTarget((m_y1+m_y2)/2);
122 m_vTargetY.push_back(iNewTarget);
123 bool bDrawHelper;
125 if (abs(iNewTarget - CDasherModel::ORIGIN_Y) >= GetLongParameter(LP_GAME_HELP_DIST)) {
126 //offscreen
127 if (abs(iNewTarget - CDasherModel::ORIGIN_Y) >= abs(m_iTargetY - CDasherModel::ORIGIN_Y)) {
128 //not decreasing
129 if (m_uHelpStart == std::numeric_limits<unsigned long>::max())
130 m_uHelpStart = lTime + GetLongParameter(LP_GAME_HELP_TIME);
131 } else {
132 //they're heading in the right direction
133 if (m_uHelpStart >= lTime) //never displayed help, so assume they don't need it
134 m_uHelpStart = std::numeric_limits<unsigned long>::max();
135 //else, we were displaying help; keep so doing.
137 bDrawHelper = m_uHelpStart <= lTime;
138 } else {
139 //onscreen
140 m_uHelpStart = std::numeric_limits<unsigned long>::max();
141 bDrawHelper=false;
144 m_iTargetY = iNewTarget;
145 if (bDrawHelper) {
146 //draw a line along the y axis
147 myint x[2], y[2];
148 x[0] = x[1] = -100;
150 const int lineWidth(GetLongParameter(LP_LINE_WIDTH));
151 myint minX,minY,maxX,maxY;
152 pView->VisibleRegion( minX, minY, maxX, maxY);
154 if (m_y1 > maxY) {
155 //off the top! make arrow point straight up...
156 y[1] = CDasherModel::MAX_Y;
157 y[0] = y[1] - 400;
158 } else if (m_y2 < minY) {
159 //off the bottom! make arrow point straight down...
160 y[1] = 0;
161 y[0] = 400;
162 } else {
163 //draw line parallel to that region of y-axis
164 y[0] = m_y1; y[1] = m_y2;
165 pView->DasherPolyline(x, y, 2, lineWidth, m_iCrosshairColor);
166 //and make arrow horizontal, pointing to the midpoint
167 x[0] = -400;
168 y[0] = y[1] = m_iTargetY;
170 pView->DasherPolyarrow(x, y, 2, 3*lineWidth, m_iCrosshairColor, 0.2);
172 if (GetBoolParameter(BP_GAME_HELP_DRAW_PATH)) DrawBrachistochrone(pView);
175 //reset location accumulators ready for next frame
176 m_y1 = std::numeric_limits<myint>::min();
177 m_y2 = std::numeric_limits<myint>::max();
179 // Check if we've reached the end of a chunk
180 if(m_iLastSym == m_vTargetSymbols.size() - 1) {
181 m_pInterface->Message(ComputeStats(m_vTargetY),true);
182 m_vTargetY.clear(); //could preserve if samples not excessive...but is it meaningful (given restart)?
183 m_pInterface->GetActiveInputMethod()->pause();
184 m_ulTotalTime += (lTime - m_ulSentenceStartTime);
185 m_dTotalNats += (pModel->GetNats() - m_dSentenceStartNats);
186 m_uiTotalSyms += m_vTargetSymbols.size();
187 if (!GenerateChunk()) {
188 m_pInterface->Message("Game mode sentence file finished!",true);
189 //note this deletes the 'this' pointer...
190 m_pInterface->LeaveGameMode();
191 //so better get out of here, fast!
192 return;
196 DrawText(pView);
199 void CGameModule::DrawBrachistochrone(CDasherView *pView) {
200 // Plot a brachistochrone - the optimal path from the crosshair to the target
201 // this is a circle, passing through both crosshair and target, centered on the y-axis
202 const myint CenterY = ComputeBrachCenter();
203 pView->DasherSpaceArc(CenterY, abs(CenterY - m_iTargetY), CDasherModel::ORIGIN_X, CDasherModel::ORIGIN_Y, 0, m_iTargetY, m_iCrosshairColor, 2*(int)GetLongParameter(LP_LINE_WIDTH));
206 void CGameModule::DrawHelperArrow(Dasher::CDasherView* pView)
208 // This plots a helpful pointer to the best direction to take to get to the target.
209 // Probably too much floating point maths here, sort later.
210 // Start of line is the crosshair location
211 const int gameColour = 135; //Neon green. (!)
212 const int noOfPoints = 10; // The curve will be made up of 9 straight segments...
213 const myint m_iCrossX(CDasherModel::ORIGIN_X),m_iCrossY(CDasherModel::ORIGIN_Y);
215 struct {
216 myint iTargetY;
217 myint iCenterY;
218 } m_Target;
219 m_Target.iTargetY = m_iTargetY;
220 m_Target.iCenterY = ComputeBrachCenter();
221 myint iX[noOfPoints];
222 myint iY[noOfPoints];
223 myint iLength;
225 // Arrow starts at the cross hairs
226 iX[0] = m_iCrossX;
227 iY[0] = m_iCrossY;
229 myint a = m_iCrossX/5;
230 myint defaultlength = m_iCrossX - a ;
232 // ... then decide the length of the arrow...
233 myint r = m_Target.iTargetY-m_Target.iCenterY; // radius of our circle (+ or -)
235 if(m_Target.iTargetY < a && m_Target.iCenterY < m_iCrossY-defaultlength/2)
237 myint x = (myint) sqrt((double)(r*r-pow((double)(m_Target.iCenterY-a),2)));
238 iLength = (myint) sqrt((double)(pow((double)(x-m_iCrossX),2)+pow((double)(a-m_iCrossY),2)));
240 else if(m_Target.iTargetY > 2*m_iCrossY-a && m_Target.iCenterY > m_iCrossY+defaultlength/2)
242 myint x = (myint) sqrt((double)(r*r-pow((double)(m_Target.iCenterY+a-2*m_iCrossY),2)));
243 iLength = (myint) sqrt((double)(pow((double)(x-m_iCrossX),2)+pow((double)(a-m_iCrossY),2)));
245 else
246 iLength = defaultlength;
248 //...then calculate the points required...
249 double angle = ((double)iLength/(double)r)/(double)noOfPoints;
251 for(int n = 1; n < noOfPoints; ++n)
253 iX[n] = (myint) (cos(angle)*(iX[n-1]) - sin(angle)*(iY[n-1]-m_Target.iCenterY));
254 iY[n] = (myint) (m_Target.iCenterY + sin(angle)*(iX[n-1]) + cos(angle)*(iY[n-1]-m_Target.iCenterY));
256 //...then plot it.
257 pView->DasherPolyarrow(iX, iY, noOfPoints, GetLongParameter(LP_LINE_WIDTH)*4, gameColour, 1.414);
261 myint CGameModule::ComputeBrachCenter() {
262 const myint iCrossX(CDasherModel::ORIGIN_X), iCrossY(CDasherModel::ORIGIN_Y);
263 // This formula computes the Dasher Y Coordinate of the center of the circle on which
264 // the dasher brachistochrone lies : iCenterY
266 // It comes from the pythagorean relation: iCrossX^2 + (iCenterY - iCrossY)^2 = r^2
267 // where r is the radius of the circle, r = abs(iTargetY-iCenterY)
268 return 0.5*(double(iCrossX*iCrossX)/double(iCrossY-m_iTargetY)+iCrossY+m_iTargetY);
271 bool CGameModule::GenerateChunk() {
272 m_iLastSym = -1;
273 m_vTargetSymbols.clear();
274 m_strWrong="";
275 m_pWordGenerator->GetSymbols(m_vTargetSymbols);
276 m_pInterface->ClearAllContext();
277 m_ulSentenceStartTime = 0;
278 if (m_vTargetSymbols.empty()) return false;
279 ChunkGenerated();
280 return true;