Wiley.Symbian.OS.C.plus.plus.for.Mobile.Phones.Aug.2007 (779890), страница 64
Текст из файла (страница 64)
Each tile state contains one of thethree values ETileBlank, ETileNought or ETileCross.The key functions are TryMakeMove() and GameWonBy(), both ofwhich are called from the controller. TryMakeMove() only allows amove to be made if the corresponding tile is initially blank. If the moveis allowed, the function sets the tile-state variable corresponding to theIMPLEMENTING THE GAME ON S60343tile to either ETileCross or ETileNought, depending on the valuepassed in aCrossTurn.TBool COandXEngine::TryMakeMove(TInt aIndex, TBool aCrossTurn){if (iTileStates[aIndex] == ETileBlank){iTileStates[aIndex] = aCrossTurn ? ETileCross : ETileNought;return ETrue;}return EFalse;}GameWonBy() checks if there is a winner and, if so, whether thewinner is the Nought player or the Cross player.
It does so by determiningif there is a horizontal, vertical or diagonal line of three noughts orthree crosses. If there is such a line, it returns either ETileNought orETileCross; otherwise it returns zero, to indicate that, as yet, there isno winner.TTileState COandXEngine::GameWonBy() const{const TInt KNoughtWinSum = KTilesPerSide * ETileNought;const TInt KCrossWinSum = KTilesPerSide * ETileCross;// is there a row or column of matching tiles?for (TInt i = 0; i < KTilesPerSide; ++i){TInt rowSum = 0;TInt colSum = 0;for (TInt j = 0; j < KTilesPerSide; ++j){rowSum += TileState(j, i);colSum += TileState(i, j);}if (rowSum == KNoughtWinSum | | colSum == KNoughtWinSum)return ETileNought;if (rowSum == KCrossWinSum | | colSum == KCrossWinSum)return ETileCross;}// is there a diagonal of matching tiles?TInt blTrSum = 0;// bottom left to top rightTInt tlBrSum = 0;// top left to bottom rightfor (TInt i = 0; i < KTilesPerSide; ++i){tlBrSum += TileState(i, i);blTrSum += TileState(i, KTilesPerSide - 1 - i);}if (blTrSum == KNoughtWinSum | | tlBrSum == KNoughtWinSum)return ETileNought;if (blTrSum == KCrossWinSum | | tlBrSum == KCrossWinSum)return ETileCross;return ETileBlank; // No winner}344A SIMPLE GRAPHICAL APPLICATIONAs with the controller class, the ExternalizeL() and InternalizeL() functions of the engine class, called from the application UIclass, simply write and read the state – in this case, the values of each ofthe board’s tiles – to and from the appropriate stream:void COandXEngine::ExternalizeL(RWriteStream& aStream) const{for (TInt i = 0; i<KNumberOfTiles; i++){aStream.WriteInt8L(iTileStates[i]);}}void COandXEngine::InternalizeL(RReadStream& aStream){for (TInt i = 0; i<KNumberOfTiles; i++){iTileStates[i] = static_cast<TTileState>(aStream.ReadInt8L());}}Like the controller class, the engine class is constructed by means ofa call to its static NewL() function and, again, there are no potentiallyleaving calls to be made from a second-phase constructor.
For the engineclass, we’ve chosen to illustrate an alternative way of implementingconstruction in such a case, where the non-leaving calls are made fromthe constructor rather than from the NewL() function:COandXEngine* COandXEngine::NewL(){return new(ELeave) COandXEngine;}COandXEngine::COandXEngine(){Reset();}The View ClassViews and the view architecture are discussed in Chapter 14 and thedetailed behavior of controls is described in Chapters 15, 17 and 18.
Inthis chapter, therefore, we only briefly describe the general aspects ofcontrols and views, concentrating on those features that are specific tothe Noughts and Crosses application.An application’s view is an instance of a window-owning control(as defined in Chapter 15) and derives, directly or indirectly, from theCCoeControl class.
For the Noughts and Crosses application, the viewis a compound control, with the view’s area being tiled with a number ofsubsidiary controls.IMPLEMENTING THE GAME ON S60345We’ve chosen to implement the S60 view class by deriving directlyfrom CCoeControl. S60 applications can derive from more specificclasses, such as CAknView, but we decided not to for this application inorder to reduce the differences between the S60 and UIQ versions, andbecause many of the S60-specific views are more suited to text displays.The view also inherits from an MViewCmdHandler interface class, whichwe describe later.CCoeControlCOandXSymbolControlCOandXTileCOandXStatusWinMViewCmdHandlernCOandXAppView1Figure 12.3The View class of the Noughts and Crosses applicationFigure 12.3 shows that all the components of the view are themselvesalso controls, in the sense that they all derive from CCoeControl.
Theview owns a single status-display control and a series of tiles.Both the status display control and the tiles need to display either anought or a cross, so we’ve chosen to implement them as subclassesof a generic symbol-drawing control, which knows how to draw thosesymbols. The two subclasses simply add the specification of the size andlocation, within their own area, of the symbol.As you can see from the class definition of the symbol-drawing baseclass, its only member function is to draw a nought or a cross symbol:class COandXSymbolControl : public CCoeControl{protected:void DrawSymbol(CWindowGc& aGc, const TRect& aRect,TBool aDrawCross) const;};The implementation of this function is:void COandXSymbolControl::DrawSymbol(CWindowGc& aGc,const TRect& aRect, TBool aDrawCross) const346A SIMPLE GRAPHICAL APPLICATION{TRect drawRect(aRect);// Shrink by about 15%drawRect.Shrink(aRect.Width()/6, aRect.Height()/6);// Pen size set to just over 10% of the shape’s sizeTSize penSize(aRect.Width()/9, aRect.Height()/9);aGc.SetPenSize(penSize);aGc.SetPenStyle(CGraphicsContext::ESolidPen);if (aDrawCross){aGc.SetPenColor(KRgbGreen);// Cosmetic reduction of cross size by half the line widthdrawRect.Shrink(penSize.iWidth/2, penSize.iHeight/2);aGc.DrawLine(drawRect.iTl, drawRect.iBr);TInt temp;temp = drawRect.iTl.iX;drawRect.iTl.iX = drawRect.iBr.iX;drawRect.iBr.iX = temp;aGc.DrawLine(drawRect.iTl, drawRect.iBr);}else // draw a circle{aGc.SetPenColor(KRgbRed);aGc.SetBrushStyle(CGraphicsContext::ESolidBrush);aGc.DrawEllipse(drawRect);}};You see this code again in Chapter 15, where the more detailed aspectsof drawing this control are discussed; here, it is sufficient to note that itis written to be as independent as possible of the size or position of thesymbol to be drawn.
The fact that the same code successfully draws boththe large symbols in the tiles and the much smaller symbols in the statusarea shows that this approach is both feasible and sensible.The symbol is centered in the rectangle (which is always a square)passed to the function in aRect, but shrunk to leave a small border,and the pen width is set to match the size of the symbol. Note that thecross symbol is shrunk by a further small amount since, if the circlesand crosses were both drawn to exactly the same size, the cross wouldlook larger to the eye. This kind of attention to detail can make a largedifference to the appearance of any application.The tile and status window control classes both derive from COandXSymbolControl:class COandXTile : public COandXSymbolControl{public:COandXTile();IMPLEMENTING THE GAME ON S60347∼COandXTile();void ConstructL(RWindow& aWindow);// New functionvoid SetOwnerAndObserver(COandXAppView* aControl);// From CCoeControlTKeyResponse OfferKeyEventL(const TKeyEvent& aKeyEvent,TEventCode aType);TCoeInputCapabilities InputCapabilities() const;protected:void FocusChanged(TDrawNow aDrawNow);void HandlePointerEventL(const TPointerEvent& aPointerEvent);private:void Draw(const TRect& aRect) const;// New functionvoid TryHitL();private:MViewCmdHandler* iCmdHandler;};class COandXStatusWin : public COandXSymbolControl{public:static COandXStatusWin* NewL(RWindow& aWindow);∼COandXStatusWin();private:COandXStatusWin();void ConstructL(RWindow& aWindow);void Draw(const TRect& aRect) const;}Note that, even though S60 devices do not normally use touchscreens, we include code that handles input from keys – via OfferKeyEventL() – and any pointer device – via HandlePointerEventL().It is quite safe to do this: if a particular device does not support oneor the other input device, the relevant code simply is not called.Including both options makes it easier to port the code to differentdevices.The status window’s Draw() function simply calculates the square inwhich to draw the symbol and then calls DrawSymbol(), obtaining theflag indicating which symbol to draw by calling the IsCrossTurn()function of the controller class:void COandXStatusWin::Draw(const TRect& /*aRect*/) const{CWindowGc& gc = SystemGc();TRect boxRect = Rect();gc.Clear(boxRect);TInt boxHeight = boxRect.iBr.iY - boxRect.iTl.iY;boxRect.iTl.iX = boxRect.iBr.iX - boxHeight;DrawSymbol(gc, boxRect, Controller().IsCrossTurn());}348A SIMPLE GRAPHICAL APPLICATIONThe Draw() function for each tile works in a similar way:void COandXTile::Draw(const TRect& /*aRect*/) const{TTileState tileType;tileType = iCmdHandler->TileStatus(this);CWindowGc& gc = SystemGc();TRect rect = Rect();if (IsFocused()){gc.SetBrushColor(KRgbYellow);}gc.Clear(rect);if (tileType!=ETileBlank){DrawSymbol(gc, rect, tileType==ETileCross);}}In this case, the symbol to be drawn is determined by a call to theTileStatus() function of the view class, by a mechanism that isdescribed later.