commands/snake/snake.cpp
author Tom Sutcliffe <thomas.sutcliffe@accenture.com>
Thu, 26 Aug 2010 00:49:35 +0100
changeset 37 534b01198c2d
parent 0 7f656887cf89
child 66 2a78c4ff2eab
permissions -rw-r--r--
Added ENotifyKeypresses and ECaptureCtrlC flags to CCommandBase. Commands can now get keypresses and handle ctrl-C via callbacks instead of having to implement custom active objects. As part of this extended the CCommandBase extension interface to MCommandExtensionsV2 for the new virtual functions KeyPressed(TUint aKeyCode, TUint aModifiers) and CtrlCPressed(). sudo now cleans up correctly by using ECaptureCtrlC.

// snake.cpp
// 
// Copyright (c) 2009 - 2010 Accenture. All rights reserved.
// This component and the accompanying materials are made available
// under the terms of the "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
// 
// Initial Contributors:
// Accenture - Initial contribution
//

#include <fshell/ioutils.h>
#include <e32math.h>

using namespace IoUtils;

_LIT(KSnakeSeg, "*");
_LIT(KBait, "$");

class CKeyWatcher;

class CCmdSnake : public CCommandBase
	{
public:
	static CCommandBase* NewLC();
	~CCmdSnake();
	
	void Up();
	void Down();
	void Left();
	void Right();
	void Exit();
	void OnTimer();
private:
	CCmdSnake();
private: // From CCommandBase.
	virtual const TDesC& Name() const;
	virtual void DoRunL();
	virtual void ArgumentsL(RCommandArgumentList& aArguments);
	virtual void OptionsL(RCommandOptionList& aOptions);
	
	void ConstructL();
	void InitBoardL();
	void SetBoard(TInt aX, TInt aY, TBool aSet);
	TBool GetBoard(TInt aX, TInt aY);
	void DrawBoardL();
	void InitSnakeL(TInt aLen, TPoint aPos);
	void DrawSnakeL();
	void DrawScore();
	void PlaceBait();
	void Grow();
	
	void Dead();
private:
	RIoConsoleWriteHandle iCons;
	RIoConsoleReadHandle iConsIn;
	TSize iBoardSize;
	RArray<TPoint> iSnake;
	TInt iSnakeHead;
	RArray<TUint32> iBoard;
	TInt iBoardWidthWords;
	TPoint iDirection;
	TPoint iBait;
	TInt iScore;
	TInt iSpeed;
	
	CKeyWatcher* iKeys;
	CPeriodic* iTimer;
	TInt64 iRandomSeed;
	TBool iInCollisionGracePeriod;
	};

class CKeyWatcher : public CActive
	{
public:
	CKeyWatcher(CCmdSnake& aOwner, RIoConsoleReadHandle& aCons);
	~CKeyWatcher();
	void Request();

private:
	virtual void RunL();
	virtual void DoCancel();
private:
	CCmdSnake& iOwner;
	RIoConsoleReadHandle& iCons;
	};


CCommandBase* CCmdSnake::NewLC()
	{
	CCmdSnake* self = new(ELeave) CCmdSnake();
	CleanupStack::PushL(self);
	self->ConstructL();
	return self;
	}

CCmdSnake::~CCmdSnake()
	{
	delete iKeys;
	delete iTimer;
	iSnake.Close();
	iBoard.Close();
	}

CCmdSnake::CCmdSnake()
	: CCommandBase(EManualComplete), iSpeed(50000)
	{
	}

const TDesC& CCmdSnake::Name() const
	{
	_LIT(KName, "snake");	
	return KName;
	}
	
TInt OnTimerS(TAny* aSnake)
	{
	((CCmdSnake*)aSnake)->OnTimer();
	return KErrNone;
	}

void CCmdSnake::DoRunL()
	{
	if (!Stdout().AttachedToConsole())
		{
		LeaveIfErr(KErrNotSupported, _L("Stdout must be connected to a console"));
		}
	iCons = Stdout();
	if (!Stdin().AttachedToConsole())
		{
		LeaveIfErr(KErrNotSupported, _L("Stdin must be connected to a console"));
		}
	iConsIn = Stdin();
	iKeys = new(ELeave)CKeyWatcher(*this, iConsIn);
	iCons.SetCursorHeight(0);
	
	User::LeaveIfError(iCons.GetScreenSize(iBoardSize));
	iBoardSize.iHeight--; // space for status bar, plus to ensure that the console doesn't scroll when we draw the bottom line
	iBoardWidthWords = iBoardSize.iWidth / 32;
	if (iBoardSize.iWidth % 32) ++iBoardWidthWords;
	InitBoardL();
	
	DrawBoardL();
	
	TPoint centre;
	centre.iX = iBoardSize.iWidth / 2;
	centre.iY = iBoardSize.iHeight / 2;
	InitSnakeL(10, centre);	
	DrawSnakeL();
	PlaceBait();
	DrawScore();
	
	iTimer = CPeriodic::NewL(CActive::EPriorityStandard);
	iTimer->Start(iSpeed, iSpeed, TCallBack(OnTimerS, this));
	}

void CCmdSnake::ArgumentsL(RCommandArgumentList& /*aArguments*/)
	{
	}

void CCmdSnake::OptionsL(RCommandOptionList& aOptions)
	{
	_LIT(KOptSpeed, "speed");
	aOptions.AppendIntL(iSpeed, KOptSpeed);
	}
	
void CCmdSnake::ConstructL()
	{
	BaseConstructL();
	TTime now;
	now.HomeTime();
	iRandomSeed = now.Int64();
	}

void CCmdSnake::InitBoardL()
	{
	// first, zero the whole board
	iBoard.Reset();
	for (TInt y=0; y<iBoardSize.iHeight; ++y)
		{
		for (TInt x=0; x<iBoardWidthWords; ++x)
			{
			iBoard.AppendL(0);
			}
		}
	// draw a box around the edge
	for (TInt i=0; i<iBoardSize.iWidth; ++i)
		{
		SetBoard(i, 0, ETrue);
		SetBoard(i, iBoardSize.iHeight-1, ETrue);
		}
	for (TInt i=0; i<iBoardSize.iHeight; ++i)
		{
		SetBoard(0, i, ETrue);
		SetBoard(iBoardSize.iWidth-1, i, ETrue);
		}
	}
	
void CCmdSnake::SetBoard(TInt aX, TInt aY, TBool aSet)
	{
	TInt word = (aY * iBoardWidthWords) + (aX / 32);
	TInt bit = 1 << (aX % 32);
	if (aSet)
		{
		iBoard[word] |= bit;
		}
	else
		{
		iBoard[word] &= (~bit);
		}
	}
	
TBool CCmdSnake::GetBoard(TInt aX, TInt aY)
	{
	TInt word = (aY * iBoardWidthWords) + (aX / 32);
	TInt bit = 1 << (aX % 32);
	return iBoard[word] & bit;
	}
	
void CCmdSnake::DrawBoardL()
	{
	User::LeaveIfError(iCons.ClearScreen());
	
	RBuf line;
	line.CreateMaxL(iBoardSize.iWidth);
	CleanupClosePushL(line);
	
	for (TInt y=0; y<iBoardSize.iHeight; ++y)
		{
		for (TInt x=0; x<iBoardSize.iWidth; ++x)
			{
			line[x] = GetBoard(x,y) ? '#' : ' ';
			}
		User::LeaveIfError(iCons.SetCursorPosAbs(TPoint(0, y)));
		User::LeaveIfError(iCons.Write(line));
		}
	
	
	CleanupStack::PopAndDestroy();
	}
	
void CCmdSnake::InitSnakeL(TInt aLen, TPoint aPos)
	{
	iSnake.Reset();
	for (TInt i=0; i<aLen; ++i)
		{
		iSnake.AppendL(aPos);
		}
	iSnakeHead = 0;
	}
	
void CCmdSnake::DrawSnakeL()
	{
	for (TInt i=0; i<iSnake.Count(); ++i)
		{
		User::LeaveIfError(iCons.SetCursorPosAbs(iSnake[i]));
		User::LeaveIfError(iCons.Write(KSnakeSeg));
		}
	}

void CCmdSnake::Up()
	{
	if (iDirection.iY != 1)
		{
		iDirection.SetXY(0, -1);
		}
	}
	
void CCmdSnake::Down()
	{
	if (iDirection.iY != -1)
		{
		iDirection.SetXY(0, 1);
		}
	}
	
void CCmdSnake::Left()
	{
	if (iDirection.iX != 1)
		{
		iDirection.SetXY(-1, 0);
		}
	}
	
void CCmdSnake::Right()
	{
	if (iDirection.iX != -1)
		{
		iDirection.SetXY(1, 0);
		}
	}
	
_LIT(KQuitFmt, "Quit; score: %d\n");

void CCmdSnake::Exit()
	{
	iCons.SetCursorPosAbs(TPoint(0, iBoardSize.iHeight));
	iCons.SetCursorHeight(20);
	TBuf<0x20> buf;
	buf.AppendFormat(KQuitFmt, iScore);
	iCons.Write(buf);
	Complete(iScore);
	}
	
_LIT(KDeadFmt, "Game over; score: %d\n");

void CCmdSnake::Dead()
	{
	iKeys->Cancel();
	iCons.SetCursorPosAbs(TPoint(0, iBoardSize.iHeight));
	iCons.SetCursorHeight(20);
	TBuf<0x20> buf;
	buf.AppendFormat(KDeadFmt, iScore);
	iCons.Write(buf);
	Complete(iScore);
	}
	
void CCmdSnake::OnTimer()
	{
	iKeys->Request();
	if (!(iDirection.iX || iDirection.iY)) return;
	TPoint newHead = iSnake[iSnakeHead] + iDirection + iBoardSize;
	newHead.iX %= iBoardSize.iWidth;
	newHead.iY %= iBoardSize.iHeight;
	
	// check for obstacles
	if (GetBoard(newHead.iX, newHead.iY))
		{
		if (iInCollisionGracePeriod)
			{
			Dead();
			}
		else
			{
			iInCollisionGracePeriod = ETrue;
			}
		return;
		}
	iInCollisionGracePeriod = EFalse;
	for (TInt i=0; i<iSnake.Count(); ++i)
		{
		if (newHead == iSnake[i])
			{
			Dead();
			return;
			}
		}
	
	TInt tail = (iSnakeHead+1) % iSnake.Count();
	// overwrite the tail if it's not in the same position as the segment before it
	if (iSnake[tail] != iSnake[(tail+1)%iSnake.Count()])
		{
		iCons.SetCursorPosAbs(iSnake[tail]);
		iCons.Write(_L(" "));
		}
	iCons.SetCursorPosAbs(newHead);
	iCons.Write(KSnakeSeg);
	iSnakeHead = (iSnakeHead+1) % iSnake.Count();
	iSnake[iSnakeHead] = newHead;
	
	if (iSnake[iSnakeHead] == iBait)
		{
		// Yummy!
		iScore++;
		DrawScore();
		PlaceBait();
		Grow();
		}
	}
	
_LIT(KScoreFormat, "Score: %d");
	
void CCmdSnake::DrawScore()
	{
	iCons.SetCursorPosAbs(TPoint(0, iBoardSize.iHeight));
	TBuf<0x20> score;
	score.AppendFormat(KScoreFormat, iScore);
	iCons.Write(score);
	}

void CCmdSnake::PlaceBait()
	{
	TBool ok;
	do
		{
		ok = ETrue;
		iBait.iX = Math::Rand(iRandomSeed) % iBoardSize.iWidth;
		iBait.iY = Math::Rand(iRandomSeed) % iBoardSize.iHeight;
		
		if (GetBoard(iBait.iX, iBait.iY)) ok = EFalse;
		if (ok)
			{
			for (TInt i=0; i<iSnake.Count(); ++i)
				{
				if (iSnake[i] == iBait) ok = EFalse;
				}
			}
		} while (!ok);
	iCons.SetCursorPosAbs(iBait);
	iCons.Write(KBait);
	}

void CCmdSnake::Grow()
	{
	TInt tail = (iSnakeHead+1)% iSnake.Count();
	iSnake.Insert(iSnake[tail], tail);
	if (tail < iSnakeHead) iSnakeHead++;
	}



EXE_BOILER_PLATE(CCmdSnake)


//______________________________________________________________________________
//						CKeyWatcher
CKeyWatcher::CKeyWatcher(CCmdSnake& aOwner, RIoConsoleReadHandle& aCons)
	: CActive(EPriorityHigh), iOwner(aOwner), iCons(aCons)
	{
	CActiveScheduler::Add(this);
	Request();
	}

CKeyWatcher::~CKeyWatcher()
	{
	Cancel();
	}

void CKeyWatcher::RunL()
	{
	User::LeaveIfError(iStatus.Int());
	TKeyCode key = (TKeyCode)iCons.KeyCode();
	switch (key)
		{
	case EKeyUpArrow:
	case '2':
		iOwner.Up();
		break;
	case EKeyDownArrow:
	case '8':
		iOwner.Down();
		break;
	case EKeyLeftArrow:
	case '4':
		iOwner.Left();
		break;
	case EKeyRightArrow:
	case '6':
		iOwner.Right();
		break;
	case EKeyEscape:
	case EKeyBackspace:
		iOwner.Exit();
		break;
	default:
		break;
		}
	}

void CKeyWatcher::DoCancel()
	{
	iCons.WaitForKeyCancel();
	}

void CKeyWatcher::Request()
	{
	if (!IsActive())
		{
		iCons.WaitForKey(iStatus);
		SetActive();
		}
	}