// A very simple Concentration game, clone of Lisp one discussed in Lisp/C++ debate // I used MSVC AppWizard to make a dialog-box app, but the game has its own CWnd-based class // ..so it can be slung into any MFC framework with ease // DibDC is a CDC-derived class I use, so it can replace CDC in the MFC framework, // ..but also has a bunch of extra useful functions, and supports a sprite class // I guess that's my plug for extensibility via OO and polymorphism ;-) // The wizard-generated dialog box needs the following added: // Added to CConcenDlg::OnInitDlg() // CConcenDlg has a member ConcWnd m_ConcWnd, and includes its header file // Otherwise it is as the MSVC App Wizard made it { // Set RNG srand( time( NULL ) ); rand(); rand(); // Choose game params and tell the game window CSize gridSize( 4, 4 ); CSize imageSize( 150, 150 ); CString imageDir( "." ); // put images in same directory as Concen.exe m_ConcWnd.SetParameters( gridSize, imageSize, imageDir ); // Resize this dialog appropriately, and create game window with dummy ID { CSize gameWndSize( gridSize.cx * imageSize.cx, gridSize.cy * imageSize.cy ); // Getting metrics is a bit untidy CRect clientRect; GetClientRect( & clientRect ); ClientToScreen( & clientRect ); CRect wndRect; GetWindowRect( & wndRect ); CRect dlgRect( CPoint( 0, 0 ), gameWndSize + wndRect.Size() - clientRect.Size() ); SetWindowPos( & wndTop, 0, 0, dlgRect.Width(), dlgRect.Height(), SWP_NOREDRAW ); CRect gameWndRect( CPoint( 0, 0 ), gameWndSize ); m_ConcWnd.Create( NULL, "", WS_CHILD | WS_VISIBLE, gameWndRect, this, IDC_NULL ); } } // Here's the ConcWnd.h file (left out some minor auto-generated #defines) // A very basic 'Concentration' game for one player // Clone of Lisp version as discussed on newsgroup #include using namespace std; #include "Util-DibDC.h" // Max size of grid on either dimension (I know, too lazy to create a 2D grid class or template) #define CW_GRIDMAX 8 // Simple 'Concentration' game class ConcWnd : public CWnd { public: protected: CSize m_GridSize; // size of grid CSize m_ImageSize; // size of an image CString m_ImageDir; // image directory ("." for same directory as program) vector< DibDC > m_Images; // the images currently in use // DibDC is a CDC-derived class I use int m_ImageVals[ CW_GRIDMAX ][ CW_GRIDMAX ]; // which of m_Images is at any given square bool m_ImagePaired[ CW_GRIDMAX ][ CW_GRIDMAX ]; // true if an image has been paired CPoint m_FirstPick; // first card picked, (-1,-1) if no pick so far CPoint m_SecondPick; // second card picked, (-1,-1) if no pick int m_nAnimTicks; // counts down half a second if bad pair is picked CRect m_Image00; // position of top left image, for locating mouse hits and painting public: // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(ConcWnd) //}}AFX_VIRTUAL protected: // Generated message map functions //{{AFX_MSG(ConcWnd) afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); afx_msg void OnPaint(); afx_msg void OnLButtonUp(UINT nFlags, CPoint point); afx_msg void OnTimer(UINT nIDEvent); //}}AFX_MSG DECLARE_MESSAGE_MAP() public: ConcWnd(); virtual ~ConcWnd(); void SetParameters( const CSize & gridSize, const CSize & imageSize, const CString & imageDir ); protected: CPoint GetCardHit( const CPoint & mouse ) const; void InitGame(); }; // And here's the ConcWnd.cpp file, which is the guts of the game #include "stdafx.h" #include "Concen.h" #include "ConcWnd.h" #include #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // ConcWnd ConcWnd::ConcWnd() { } ConcWnd::~ConcWnd() { } BEGIN_MESSAGE_MAP(ConcWnd, CWnd) //{{AFX_MSG_MAP(ConcWnd) ON_WM_CREATE() ON_WM_PAINT() ON_WM_LBUTTONUP() ON_WM_TIMER() //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // ConcWnd message handlers // Called when game window is first created int ConcWnd::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CWnd::OnCreate(lpCreateStruct) == -1) return -1; // TODO: Add your specialized creation code here InitGame(); SetTimer( TIMER_CONCWND, 40, NULL ); return 0; } // Called when game window is to be painted, which happens soon after any time I call Invalidate() void ConcWnd::OnPaint() { CPaintDC dc(this); // device context for painting // TODO: Add your message handler code here // I just redraw everything, there's nothing much that can flicker in this game! { for ( int x = 0; x < m_GridSize.cx; x++ ) { for ( int y = 0; y < m_GridSize.cy; y++ ) { CRect rect( m_Image00 ); rect.OffsetRect( x * rect.Width(), y * rect.Height() ); if ( m_ImagePaired[ x ][ y ] || CPoint( x, y ) == m_FirstPick || CPoint( x, y ) == m_SecondPick ) { // This image is visible DibDC & srcDC = m_Images[ m_ImageVals[ x ][ y ] ]; dc.BitBlt( rect.left, rect.top, rect.Width(), rect.Height(), & srcDC, 0, 0, SRCCOPY ); } else { // Draw a black rectangle dc.FillSolidRect( & rect, RGB( 0, 0, 0 ) ); } } } } // Do not call CWnd::OnPaint() for painting messages } // Called on timer event void ConcWnd::OnTimer(UINT nIDEvent) { // TODO: Add your message handler code here and/or call default if ( nIDEvent == TIMER_CONCWND ) { // If bad pair is showing, check whether it's finished if ( m_nAnimTicks > 0 ) { m_nAnimTicks--; if ( m_nAnimTicks == 0 ) { // Back to normal mode m_FirstPick = CPoint( -1, -1 ); m_SecondPick = CPoint( -1, -1 ); Invalidate( false ); } } } CWnd::OnTimer(nIDEvent); } // Called when left mouse button is released void ConcWnd::OnLButtonUp(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default // Back to normal mode if still shoing previous bad pair if ( m_nAnimTicks > 0 ) { m_nAnimTicks = 0; m_FirstPick = CPoint( -1, -1 ); m_SecondPick = CPoint( -1, -1 ); Invalidate( false ); } // Find which card was hit CPoint card = GetCardHit( point ); if ( card.x >= 0 ) { if ( ! m_ImagePaired[ card.x ][ card.y ] ) // only select face-down cards { if ( m_FirstPick.x < 0 ) { m_FirstPick = card; // select this as first pick Invalidate( false ); // redraw } else { if ( card == m_FirstPick ) { m_FirstPick = CPoint( -1, -1 ); // cancel previous pick Invalidate( false ); } else { // Check for pair int val1 = m_ImageVals[ m_FirstPick.x ][ m_FirstPick.y ]; int val2 = m_ImageVals[ card.x ][ card.y ]; if ( val1 == val2 ) { // We got a pair! m_ImagePaired[ m_FirstPick.x ][ m_FirstPick.y ] = true; m_ImagePaired[ card.x ][ card.y ] = true; m_FirstPick = CPoint( -1, -1 ); Invalidate( false ); } else { // Show bad pair for a little while m_SecondPick = card; m_nAnimTicks = 12; Invalidate( false ); } } } } } CWnd::OnLButtonUp(nFlags, point); } /////////////////////////////////////////////////////////////////////////////////// // Functions // Initialise basic rules void ConcWnd::SetParameters( const CSize & gridSize, const CSize & imageSize, const CString & imageDir ) { m_GridSize = gridSize; m_ImageSize = imageSize; m_ImageDir = imageDir; ASSERT( ( ( m_GridSize.cx * m_GridSize.cy ) % 2 ) == 0 ); // need even grid } // Load random images and place them on the grid to start a new game void ConcWnd::InitGame() { // List all .bmps in directory vector< CString > filePaths; { CFileFind finder; bool working = finder.FindFile( m_ImageDir + "\\*.bmp" ) != 0; while ( working ) { working = finder.FindNextFile() != 0; filePaths.push_back( finder.GetFilePath() ); } } // Randomise them random_shuffle( filePaths.begin(), filePaths.end() ); // No. of image pairs needed int nWanted = m_GridSize.cx * m_GridSize.cy / 2; // No. of image pairs got int nGot = min( nWanted, filePaths.size() ); ASSERT( nGot > 0 ); // Clear grid to starting values { for ( int x = 0; x < m_GridSize.cx; x++ ) { for ( int y = 0; y < m_GridSize.cy; y++ ) { m_ImageVals[ x ][ y ] = -1; m_ImagePaired[ x ][ y ] = false; } } } // Fill up grid, two by two for ( int nPair = 0; nPair < nWanted; nPair++ ) { for ( int iSq = 0; iSq < 2; iSq++ ) { CPoint emptySq; do { emptySq = CPoint( rand() % m_GridSize.cx, rand() % m_GridSize.cy ); } while ( m_ImageVals[ emptySq.x ][ emptySq.y ] != -1 ); m_ImageVals[ emptySq.x ][ emptySq.y ] = nPair % nGot; } } // Load the requested images, leaving a 1-pixel black border at edge of each { m_Images.clear(); for ( int iFile = 0; iFile < nGot; iFile++ ) { DibDC dcLoad; dcLoad.CreateFromDIB( filePaths[ iFile ] ); DibDC dc; dc.Resize( m_ImageSize ); dc.FillSolidRect( dc.GetRect(), RGB( 0, 0, 0 ) ); CRect targetRect( dc.GetRect() ); targetRect.InflateRect( -1, -1 ); dc.StretchBlit( dcLoad, targetRect, dcLoad.GetRect() ); m_Images.push_back( dc ); } } // Metrics m_Image00 = CRect( CPoint( 0, 0 ), m_ImageSize ); // Ready to play! No card selected m_FirstPick = CPoint( -1, -1 ); m_SecondPick = CPoint( -1, -1 ); m_nAnimTicks = 0; } // Which card was hit, or (-1,-1) if no valid card CPoint ConcWnd::GetCardHit( const CPoint & mouse ) const { CPoint pix = mouse - m_Image00.TopLeft(); if ( pix.x < 0 || pix.y < 0 ) return CPoint( -1, -1 ); CPoint card( pix.x / m_Image00.Width(), pix.y / m_Image00.Height() ); if ( card.x >= m_GridSize.cx || card.y >= m_GridSize.cy ) return CPoint( -1, -1 ); return card; }