1 #include "GameModule.h"
2 #include "GameStatistics.h"
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
) {
26 *count
= sizeof(gameSets
) / sizeof(gameSets
[0]);
30 CGameModule::~CGameModule() {
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();
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...
61 } else m_strWrong
= evt
->m_sText
;
63 DASHER_ASSERT(iOffset
>= m_iLastSym
+1);
64 m_strWrong
+=evt
->m_sText
;
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
]));
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());
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
) {
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
);
125 if (abs(iNewTarget
- CDasherModel::ORIGIN_Y
) >= GetLongParameter(LP_GAME_HELP_DIST
)) {
127 if (abs(iNewTarget
- CDasherModel::ORIGIN_Y
) >= abs(m_iTargetY
- CDasherModel::ORIGIN_Y
)) {
129 if (m_uHelpStart
== std::numeric_limits
<unsigned long>::max())
130 m_uHelpStart
= lTime
+ GetLongParameter(LP_GAME_HELP_TIME
);
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
;
140 m_uHelpStart
= std::numeric_limits
<unsigned long>::max();
144 m_iTargetY
= iNewTarget
;
146 //draw a line along the y axis
150 const int lineWidth(GetLongParameter(LP_LINE_WIDTH
));
151 myint minX
,minY
,maxX
,maxY
;
152 pView
->VisibleRegion( minX
, minY
, maxX
, maxY
);
155 //off the top! make arrow point straight up...
156 y
[1] = CDasherModel::MAX_Y
;
158 } else if (m_y2
< minY
) {
159 //off the bottom! make arrow point straight down...
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
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!
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
);
219 m_Target
.iTargetY
= m_iTargetY
;
220 m_Target
.iCenterY
= ComputeBrachCenter();
221 myint iX
[noOfPoints
];
222 myint iY
[noOfPoints
];
225 // Arrow starts at the cross hairs
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)));
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
));
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() {
273 m_vTargetSymbols
.clear();
275 m_pWordGenerator
->GetSymbols(m_vTargetSymbols
);
276 m_pInterface
->ClearAllContext();
277 m_ulSentenceStartTime
= 0;
278 if (m_vTargetSymbols
.empty()) return false;