|
1 // snake.cpp |
|
2 // |
|
3 // Copyright (c) 2009 - 2010 Accenture. All rights reserved. |
|
4 // This component and the accompanying materials are made available |
|
5 // under the terms of the "Eclipse Public License v1.0" |
|
6 // which accompanies this distribution, and is available |
|
7 // at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
8 // |
|
9 // Initial Contributors: |
|
10 // Accenture - Initial contribution |
|
11 // |
|
12 |
|
13 #include <fshell/ioutils.h> |
|
14 #include <e32math.h> |
|
15 |
|
16 using namespace IoUtils; |
|
17 |
|
18 _LIT(KSnakeSeg, "*"); |
|
19 _LIT(KBait, "$"); |
|
20 |
|
21 class CKeyWatcher; |
|
22 |
|
23 class CCmdSnake : public CCommandBase |
|
24 { |
|
25 public: |
|
26 static CCommandBase* NewLC(); |
|
27 ~CCmdSnake(); |
|
28 |
|
29 void Up(); |
|
30 void Down(); |
|
31 void Left(); |
|
32 void Right(); |
|
33 void Exit(); |
|
34 void OnTimer(); |
|
35 private: |
|
36 CCmdSnake(); |
|
37 private: // From CCommandBase. |
|
38 virtual const TDesC& Name() const; |
|
39 virtual void DoRunL(); |
|
40 virtual void ArgumentsL(RCommandArgumentList& aArguments); |
|
41 virtual void OptionsL(RCommandOptionList& aOptions); |
|
42 |
|
43 void ConstructL(); |
|
44 void InitBoardL(); |
|
45 void SetBoard(TInt aX, TInt aY, TBool aSet); |
|
46 TBool GetBoard(TInt aX, TInt aY); |
|
47 void DrawBoardL(); |
|
48 void InitSnakeL(TInt aLen, TPoint aPos); |
|
49 void DrawSnakeL(); |
|
50 void DrawScore(); |
|
51 void PlaceBait(); |
|
52 void Grow(); |
|
53 |
|
54 void Dead(); |
|
55 private: |
|
56 RIoConsoleWriteHandle iCons; |
|
57 RIoConsoleReadHandle iConsIn; |
|
58 TSize iBoardSize; |
|
59 RArray<TPoint> iSnake; |
|
60 TInt iSnakeHead; |
|
61 RArray<TUint32> iBoard; |
|
62 TInt iBoardWidthWords; |
|
63 TPoint iDirection; |
|
64 TPoint iBait; |
|
65 TInt iScore; |
|
66 TInt iSpeed; |
|
67 |
|
68 CKeyWatcher* iKeys; |
|
69 CPeriodic* iTimer; |
|
70 TInt64 iRandomSeed; |
|
71 TBool iInCollisionGracePeriod; |
|
72 }; |
|
73 |
|
74 class CKeyWatcher : public CActive |
|
75 { |
|
76 public: |
|
77 CKeyWatcher(CCmdSnake& aOwner, RIoConsoleReadHandle& aCons); |
|
78 ~CKeyWatcher(); |
|
79 void Request(); |
|
80 |
|
81 private: |
|
82 virtual void RunL(); |
|
83 virtual void DoCancel(); |
|
84 private: |
|
85 CCmdSnake& iOwner; |
|
86 RIoConsoleReadHandle& iCons; |
|
87 }; |
|
88 |
|
89 |
|
90 CCommandBase* CCmdSnake::NewLC() |
|
91 { |
|
92 CCmdSnake* self = new(ELeave) CCmdSnake(); |
|
93 CleanupStack::PushL(self); |
|
94 self->ConstructL(); |
|
95 return self; |
|
96 } |
|
97 |
|
98 CCmdSnake::~CCmdSnake() |
|
99 { |
|
100 delete iKeys; |
|
101 delete iTimer; |
|
102 iSnake.Close(); |
|
103 iBoard.Close(); |
|
104 } |
|
105 |
|
106 CCmdSnake::CCmdSnake() |
|
107 : CCommandBase(EManualComplete), iSpeed(50000) |
|
108 { |
|
109 } |
|
110 |
|
111 const TDesC& CCmdSnake::Name() const |
|
112 { |
|
113 _LIT(KName, "snake"); |
|
114 return KName; |
|
115 } |
|
116 |
|
117 TInt OnTimerS(TAny* aSnake) |
|
118 { |
|
119 ((CCmdSnake*)aSnake)->OnTimer(); |
|
120 return KErrNone; |
|
121 } |
|
122 |
|
123 void CCmdSnake::DoRunL() |
|
124 { |
|
125 if (!Stdout().AttachedToConsole()) |
|
126 { |
|
127 LeaveIfErr(KErrNotSupported, _L("Stdout must be connected to a console")); |
|
128 } |
|
129 iCons = Stdout(); |
|
130 if (!Stdin().AttachedToConsole()) |
|
131 { |
|
132 LeaveIfErr(KErrNotSupported, _L("Stdin must be connected to a console")); |
|
133 } |
|
134 iConsIn = Stdin(); |
|
135 iKeys = new(ELeave)CKeyWatcher(*this, iConsIn); |
|
136 iCons.SetCursorHeight(0); |
|
137 |
|
138 User::LeaveIfError(iCons.GetScreenSize(iBoardSize)); |
|
139 iBoardSize.iHeight--; // space for status bar, plus to ensure that the console doesn't scroll when we draw the bottom line |
|
140 iBoardWidthWords = iBoardSize.iWidth / 32; |
|
141 if (iBoardSize.iWidth % 32) ++iBoardWidthWords; |
|
142 InitBoardL(); |
|
143 |
|
144 DrawBoardL(); |
|
145 |
|
146 TPoint centre; |
|
147 centre.iX = iBoardSize.iWidth / 2; |
|
148 centre.iY = iBoardSize.iHeight / 2; |
|
149 InitSnakeL(10, centre); |
|
150 DrawSnakeL(); |
|
151 PlaceBait(); |
|
152 DrawScore(); |
|
153 |
|
154 iTimer = CPeriodic::NewL(CActive::EPriorityStandard); |
|
155 iTimer->Start(iSpeed, iSpeed, TCallBack(OnTimerS, this)); |
|
156 } |
|
157 |
|
158 void CCmdSnake::ArgumentsL(RCommandArgumentList& /*aArguments*/) |
|
159 { |
|
160 } |
|
161 |
|
162 void CCmdSnake::OptionsL(RCommandOptionList& aOptions) |
|
163 { |
|
164 _LIT(KOptSpeed, "speed"); |
|
165 aOptions.AppendIntL(iSpeed, KOptSpeed); |
|
166 } |
|
167 |
|
168 void CCmdSnake::ConstructL() |
|
169 { |
|
170 BaseConstructL(); |
|
171 TTime now; |
|
172 now.HomeTime(); |
|
173 iRandomSeed = now.Int64(); |
|
174 } |
|
175 |
|
176 void CCmdSnake::InitBoardL() |
|
177 { |
|
178 // first, zero the whole board |
|
179 iBoard.Reset(); |
|
180 for (TInt y=0; y<iBoardSize.iHeight; ++y) |
|
181 { |
|
182 for (TInt x=0; x<iBoardWidthWords; ++x) |
|
183 { |
|
184 iBoard.AppendL(0); |
|
185 } |
|
186 } |
|
187 // draw a box around the edge |
|
188 for (TInt i=0; i<iBoardSize.iWidth; ++i) |
|
189 { |
|
190 SetBoard(i, 0, ETrue); |
|
191 SetBoard(i, iBoardSize.iHeight-1, ETrue); |
|
192 } |
|
193 for (TInt i=0; i<iBoardSize.iHeight; ++i) |
|
194 { |
|
195 SetBoard(0, i, ETrue); |
|
196 SetBoard(iBoardSize.iWidth-1, i, ETrue); |
|
197 } |
|
198 } |
|
199 |
|
200 void CCmdSnake::SetBoard(TInt aX, TInt aY, TBool aSet) |
|
201 { |
|
202 TInt word = (aY * iBoardWidthWords) + (aX / 32); |
|
203 TInt bit = 1 << (aX % 32); |
|
204 if (aSet) |
|
205 { |
|
206 iBoard[word] |= bit; |
|
207 } |
|
208 else |
|
209 { |
|
210 iBoard[word] &= (~bit); |
|
211 } |
|
212 } |
|
213 |
|
214 TBool CCmdSnake::GetBoard(TInt aX, TInt aY) |
|
215 { |
|
216 TInt word = (aY * iBoardWidthWords) + (aX / 32); |
|
217 TInt bit = 1 << (aX % 32); |
|
218 return iBoard[word] & bit; |
|
219 } |
|
220 |
|
221 void CCmdSnake::DrawBoardL() |
|
222 { |
|
223 User::LeaveIfError(iCons.ClearScreen()); |
|
224 |
|
225 RBuf line; |
|
226 line.CreateMaxL(iBoardSize.iWidth); |
|
227 CleanupClosePushL(line); |
|
228 |
|
229 for (TInt y=0; y<iBoardSize.iHeight; ++y) |
|
230 { |
|
231 for (TInt x=0; x<iBoardSize.iWidth; ++x) |
|
232 { |
|
233 line[x] = GetBoard(x,y) ? '#' : ' '; |
|
234 } |
|
235 User::LeaveIfError(iCons.SetCursorPosAbs(TPoint(0, y))); |
|
236 User::LeaveIfError(iCons.Write(line)); |
|
237 } |
|
238 |
|
239 |
|
240 CleanupStack::PopAndDestroy(); |
|
241 } |
|
242 |
|
243 void CCmdSnake::InitSnakeL(TInt aLen, TPoint aPos) |
|
244 { |
|
245 iSnake.Reset(); |
|
246 for (TInt i=0; i<aLen; ++i) |
|
247 { |
|
248 iSnake.AppendL(aPos); |
|
249 } |
|
250 iSnakeHead = 0; |
|
251 } |
|
252 |
|
253 void CCmdSnake::DrawSnakeL() |
|
254 { |
|
255 for (TInt i=0; i<iSnake.Count(); ++i) |
|
256 { |
|
257 User::LeaveIfError(iCons.SetCursorPosAbs(iSnake[i])); |
|
258 User::LeaveIfError(iCons.Write(KSnakeSeg)); |
|
259 } |
|
260 } |
|
261 |
|
262 void CCmdSnake::Up() |
|
263 { |
|
264 if (iDirection.iY != 1) |
|
265 { |
|
266 iDirection.SetXY(0, -1); |
|
267 } |
|
268 } |
|
269 |
|
270 void CCmdSnake::Down() |
|
271 { |
|
272 if (iDirection.iY != -1) |
|
273 { |
|
274 iDirection.SetXY(0, 1); |
|
275 } |
|
276 } |
|
277 |
|
278 void CCmdSnake::Left() |
|
279 { |
|
280 if (iDirection.iX != 1) |
|
281 { |
|
282 iDirection.SetXY(-1, 0); |
|
283 } |
|
284 } |
|
285 |
|
286 void CCmdSnake::Right() |
|
287 { |
|
288 if (iDirection.iX != -1) |
|
289 { |
|
290 iDirection.SetXY(1, 0); |
|
291 } |
|
292 } |
|
293 |
|
294 _LIT(KQuitFmt, "Quit; score: %d\n"); |
|
295 |
|
296 void CCmdSnake::Exit() |
|
297 { |
|
298 iCons.SetCursorPosAbs(TPoint(0, iBoardSize.iHeight)); |
|
299 iCons.SetCursorHeight(20); |
|
300 TBuf<0x20> buf; |
|
301 buf.AppendFormat(KQuitFmt, iScore); |
|
302 iCons.Write(buf); |
|
303 Complete(iScore); |
|
304 } |
|
305 |
|
306 _LIT(KDeadFmt, "Game over; score: %d\n"); |
|
307 |
|
308 void CCmdSnake::Dead() |
|
309 { |
|
310 iKeys->Cancel(); |
|
311 iCons.SetCursorPosAbs(TPoint(0, iBoardSize.iHeight)); |
|
312 iCons.SetCursorHeight(20); |
|
313 TBuf<0x20> buf; |
|
314 buf.AppendFormat(KDeadFmt, iScore); |
|
315 iCons.Write(buf); |
|
316 Complete(iScore); |
|
317 } |
|
318 |
|
319 void CCmdSnake::OnTimer() |
|
320 { |
|
321 iKeys->Request(); |
|
322 if (!(iDirection.iX || iDirection.iY)) return; |
|
323 TPoint newHead = iSnake[iSnakeHead] + iDirection + iBoardSize; |
|
324 newHead.iX %= iBoardSize.iWidth; |
|
325 newHead.iY %= iBoardSize.iHeight; |
|
326 |
|
327 // check for obstacles |
|
328 if (GetBoard(newHead.iX, newHead.iY)) |
|
329 { |
|
330 if (iInCollisionGracePeriod) |
|
331 { |
|
332 Dead(); |
|
333 } |
|
334 else |
|
335 { |
|
336 iInCollisionGracePeriod = ETrue; |
|
337 } |
|
338 return; |
|
339 } |
|
340 iInCollisionGracePeriod = EFalse; |
|
341 for (TInt i=0; i<iSnake.Count(); ++i) |
|
342 { |
|
343 if (newHead == iSnake[i]) |
|
344 { |
|
345 Dead(); |
|
346 return; |
|
347 } |
|
348 } |
|
349 |
|
350 TInt tail = (iSnakeHead+1) % iSnake.Count(); |
|
351 // overwrite the tail if it's not in the same position as the segment before it |
|
352 if (iSnake[tail] != iSnake[(tail+1)%iSnake.Count()]) |
|
353 { |
|
354 iCons.SetCursorPosAbs(iSnake[tail]); |
|
355 iCons.Write(_L(" ")); |
|
356 } |
|
357 iCons.SetCursorPosAbs(newHead); |
|
358 iCons.Write(KSnakeSeg); |
|
359 iSnakeHead = (iSnakeHead+1) % iSnake.Count(); |
|
360 iSnake[iSnakeHead] = newHead; |
|
361 |
|
362 if (iSnake[iSnakeHead] == iBait) |
|
363 { |
|
364 // Yummy! |
|
365 iScore++; |
|
366 DrawScore(); |
|
367 PlaceBait(); |
|
368 Grow(); |
|
369 } |
|
370 } |
|
371 |
|
372 _LIT(KScoreFormat, "Score: %d"); |
|
373 |
|
374 void CCmdSnake::DrawScore() |
|
375 { |
|
376 iCons.SetCursorPosAbs(TPoint(0, iBoardSize.iHeight)); |
|
377 TBuf<0x20> score; |
|
378 score.AppendFormat(KScoreFormat, iScore); |
|
379 iCons.Write(score); |
|
380 } |
|
381 |
|
382 void CCmdSnake::PlaceBait() |
|
383 { |
|
384 TBool ok; |
|
385 do |
|
386 { |
|
387 ok = ETrue; |
|
388 iBait.iX = Math::Rand(iRandomSeed) % iBoardSize.iWidth; |
|
389 iBait.iY = Math::Rand(iRandomSeed) % iBoardSize.iHeight; |
|
390 |
|
391 if (GetBoard(iBait.iX, iBait.iY)) ok = EFalse; |
|
392 if (ok) |
|
393 { |
|
394 for (TInt i=0; i<iSnake.Count(); ++i) |
|
395 { |
|
396 if (iSnake[i] == iBait) ok = EFalse; |
|
397 } |
|
398 } |
|
399 } while (!ok); |
|
400 iCons.SetCursorPosAbs(iBait); |
|
401 iCons.Write(KBait); |
|
402 } |
|
403 |
|
404 void CCmdSnake::Grow() |
|
405 { |
|
406 TInt tail = (iSnakeHead+1)% iSnake.Count(); |
|
407 iSnake.Insert(iSnake[tail], tail); |
|
408 if (tail < iSnakeHead) iSnakeHead++; |
|
409 } |
|
410 |
|
411 |
|
412 |
|
413 EXE_BOILER_PLATE(CCmdSnake) |
|
414 |
|
415 |
|
416 //______________________________________________________________________________ |
|
417 // CKeyWatcher |
|
418 CKeyWatcher::CKeyWatcher(CCmdSnake& aOwner, RIoConsoleReadHandle& aCons) |
|
419 : CActive(EPriorityHigh), iOwner(aOwner), iCons(aCons) |
|
420 { |
|
421 CActiveScheduler::Add(this); |
|
422 Request(); |
|
423 } |
|
424 |
|
425 CKeyWatcher::~CKeyWatcher() |
|
426 { |
|
427 Cancel(); |
|
428 } |
|
429 |
|
430 void CKeyWatcher::RunL() |
|
431 { |
|
432 User::LeaveIfError(iStatus.Int()); |
|
433 TKeyCode key = (TKeyCode)iCons.KeyCode(); |
|
434 switch (key) |
|
435 { |
|
436 case EKeyUpArrow: |
|
437 case '2': |
|
438 iOwner.Up(); |
|
439 break; |
|
440 case EKeyDownArrow: |
|
441 case '8': |
|
442 iOwner.Down(); |
|
443 break; |
|
444 case EKeyLeftArrow: |
|
445 case '4': |
|
446 iOwner.Left(); |
|
447 break; |
|
448 case EKeyRightArrow: |
|
449 case '6': |
|
450 iOwner.Right(); |
|
451 break; |
|
452 case EKeyEscape: |
|
453 case EKeyBackspace: |
|
454 iOwner.Exit(); |
|
455 break; |
|
456 default: |
|
457 break; |
|
458 } |
|
459 } |
|
460 |
|
461 void CKeyWatcher::DoCancel() |
|
462 { |
|
463 iCons.WaitForKeyCancel(); |
|
464 } |
|
465 |
|
466 void CKeyWatcher::Request() |
|
467 { |
|
468 if (!IsActive()) |
|
469 { |
|
470 iCons.WaitForKey(iStatus); |
|
471 SetActive(); |
|
472 } |
|
473 } |