Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Smasher! A Video Game in WTL

0.00/5 (No votes)
20 Mar 2002 1  
A simple arcade-style video game developed using WTL

Smasher! Game Image

Introduction

Smasher! is a simple video game developed using WTL. It is reminiscent of arcade-style video games like "Whack The Gopher". The objective of the game is to smash Debtz bugs using a smasher hammer. The game does not use DirectX or OpenGL. Our primary development goals were to build a small, self-contained executable that can be easily distributed by email or FTP, does not require a lot of supporting libraries, and runs on operating systems from Windows 95 forward. WTL helped us reach those goals.

This article discusses how we implemented dialog skins, transparent bitmaps, a memory leak checker, a mechanism for user option settings, and a self-contained help system. It also discusses the WTL Release build problem.

Dialog Skinning

Skinned applications are quite popular and there are several ways to implement them. We chose to adapt a method devised by Bjarke Viksoe. He has created a number of excellent WTL controls and a visit to his website, www.viksoe.dk, is highly recommended.

The basic premise is to create a patterned background brush and provide it in response to WM_CTLCOLORDLG messages for the dialog. Other WM_CTLCOLOR... message handlers do the same thing for other controls. Our adaptation creates the pattern brush in the application Run() function, just after the dialog is created but before it is shown, and assigns it to the dialog class using SetClassLong() as follows:

SetClassLong( dlgMain.m_hWnd, GCL_HBRBACKGROUND,
	     (LONG_PTR)CreatePatternBrush(AtlLoadBitmapImage(IDB_BACKGROUND,
	     LR_DEFAULTCOLOR | LR_SHARED)) );

There are several things happening here. A bitmap resource, IDB_BACKGROUND, is loaded from resources to create a pattern brush. The brush is assigned to the dialog class as the class background brush. After the brush is loaded in this way, it is available to all dialogs of the dialog class, #32770. The control color dialog message handler provides the brush in a manner similar to that below.

WNDCLASSEX m_wc;

LRESULT OnCtlColorDlg(UINT, WPARAM, LPARAM lParam, BOOL&)
{ // load the class info structure for class #32770 (CDialogImpl)

  GetClassInfoEx(_Module.GetModuleInstance(), "#32770", &m_wc);

  // return the background brush

  return (LRESULT)m_wc.hbrBackground; }

Message handlers for dialogs and static controls are wrapped in a template, CDialogSkin< > and typedef'd as CBackground for the Smasher! application. Each dialog that uses the skin adds CBackground to its inheritance list and chains the skin's message map. The chained map is first in the message map list, like this:

BEGIN_MSG_MAP(COptionsDlg)
  CHAIN_MSG_MAP(CBackground)
  ...
END_MSG_MAP()

Transparent Bitmap Static

Many methods have been devised for displaying transparent bitmaps. We elected to port Chris Becke's excellent implementation to WTL. His method is presented in depth in Part 2 of his tutorial: Bitmap Basics - A GDI tutorial.

CSGTransStatic, our class for transparent bitmaps static and picture controls, uses an image bitmap and a mask bitmap for transparency. It would be nice if one could assign an image list, with its built-in transparency, to any Windows control, but that is not possible.

The bitmap and its mask are created and assigned to member variables when the control is subclassed or when the bitmap is changed. Then, in OnPaint(), the two images are blitted onto the device context using SRCAND followed by SRCPAINT:

  ...
  // the mask bitmap

  hdcMem->SelectBitmap(hbmMask);
  hdc.BitBlt(0, 0, bm.bmWidth, bm.bmHeight, hdcMem->m_hDC, 0, 0, SRCAND);

  // the main image

  hdcMem->SelectBitmap(hbmImage);
  hdc.BitBlt(0, 0, bm.bmWidth, bm.bmHeight, hdcMem->m_hDC, 0, 0, SRCPAINT);
  ...

A Note On Porting To WTL

Porting to WTL from Win32 is fairly straightforward. The key thing to remember is that WTL often tries to protect you from yourself. By this we mean that it wraps Windows objects safely (most of the time) and does much of the messy work of loading structures and destroying objects. For example, in Win32, you declare a paint structure and issue BeginPaint and EndPaint instructions:

  PAINTSTRUCT ps;
  HDC hdc = BeginPaint(hwnd, &ps);
  ...
  EndPaint(hwnd, &ps);

The WTL class CPaintDC performs that for you. WTL classes usually take care of window and device context handles, too. For example, ShowWindow(hwnd, SW_SHOW) in Win32 translates to ShowWindow(SW_SHOW) in WTL.

The "Poor Man's" Leak Checker

When developing an application that relies heavily on bitmaps, cursors, and other GDI resources, one quickly discovers the value of checking for memory leaks. VC++ provides a painless way to do so in Debug mode. Simply include crtdbg.h in the application source file (we put it at the top of smasher.cpp, before stdafx.h):

#if _DEBUG
#include < crtdbg.h >

#endif

Next, place this function call as the very first thing in _tWinMain():

#if _DEBUG // start memory leak checker

  _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_CHECK_ALWAYS_DF |
		  _CRTDBG_LEAK_CHECK_DF );
#endif

While Bounds Checker and other commercial products certainly have their place, this built-in capability alerted us to several memory leaks.

Handling User Options

Smasher! has three user options. Users may change the amount of play time, the game's difficulty level, and the smasher size (i.e. the cursor). The options class, CSGOptions, follows the model of WTL's CAppModule and is defined as a global variable in the application CPP. Three member variables are loaded from and saved to the registry. The following code snippet shows how a spin control for difficulty level is set in the Options dialog:

  // read the stored settings (if available)

  _Options.ReadFromRegistry();

  // assign the difficulty level spin (up/down) control

  m_spinDifficulty = GetDlgItem(IDC_SPIN_DIFFICULTY);
  m_spinDifficulty.SetRange(1, 10);

  // set the spin value to the global option value

  m_spinDifficulty.SetPos(_Options.g_Difficulty);

Here, the value for the smasher size is extracted from its radio group. This code is only called if the user clicked OK; otherwise changes are discarded.

  // check the radio group to see which radiobutton is selected

  if (IsDlgButtonChecked(IDC_SSIZEBIG) == BST_CHECKED)
      _Options.g_SmasherSize = SMSH_BIG;

  _Options.WriteToRegistry();

Resource Management

When the lead tester got her hands on Smasher!, it did not take long for her to crash the application. This was a reminder of one of the pitfalls of developing software on Windows NT: Different Windows OSes do not handle system resources the same way. The game ran smoothly on Windows NT, swapping cursors in and out, loading bitmaps, very much enjoying itself. However, it quickly ran out of resources on the tester's Windows ME system.

Fortunately, a bit of research and the use of WTL helpers solved the problem. WTL offers a set of resource loaders for cursors, bitmaps, etc. Their general form is AtlLoadSomething. We use the WTL helpers in all cases where resources are loaded from the executable as shown in the following example. Note particularly the use of LR_SHARED, which is vitally important for Windows 9x/ME.

  UINT nFlags = LR_SHARED | LR_DEFAULTSIZE | LR_DEFAULTCOLOR;

  switch (smashersize) {
  case SMSH_BIG:
  {
    m_smasherDown = AtlLoadCursorImage(IDC_BIGDOWN, nFlags);
    m_smasherUp = AtlLoadCursorImage(IDC_BIGUP, nFlags);
    break;
  } } // end switch

Self-contained Help

Since one of the goals of the application was a self-contained executable, we could not use traditional help files or HTML help. Instead, we use a tab control, a read-only edit control, and several resource strings.

1. Tab Control

We created a tab control template, derived from CTabCtrl, and use it to switch among various help and copyright messages. It provides an erase background handler for the skin background brush. It also uses the TCS_BUTTONS style to make it appear "flat", and contains a tooltip control. To make adding tabs and tips easier, the following method is used:

LRESULT AddTab(LPTSTR tTitle, LPTSTR tTip = NULL)
{ RECT rcTab; // tab "label" rect

  TCITEM tab; // tab item structure


  tab.mask = TCIF_TEXT; // text only

  tab.pszText = tTitle;

  // tab number equals current item count

  int nTab = GetItemCount(); // 0 on first pass

  InsertItem(nTab, &tab);

  // get the rect and add the tooltip

  GetItemRect(nTab, &rcTab);
  m_tips.AddTool(m_hWnd, tTip, &rcTab, nTab + 1000);

  return nTab; }

Two interesting factors were uncovered while devising this method. Both pertain to the fourth parameter, nIDTool, of the AddTool() method of CToolTipCtrl. First, Windows NT automatically creates a tooltip for certain controls and assigns it tool ID 0. AddTool() compensates by restricting use of tool ID 0. Second, tooltip IDs must be equal to or greater than the total number of tooltips. In other words, a tab control with three tabs should have tool IDs no lower than 3, 4, and 5 or the tips will not display correctly.

2. Edit Control

Various Smasher! help and copyright messages are displayed in a slightly customized edit. We wanted a white background instead of the normal gray of read-only edits. CSGEdit becomes essentially read-only by throwing away selection, character, and mouse messages like this:

  BEGIN_MSG_MAP(CSGEdit)
    MESSAGE_HANDLER(EM_SETSEL, OnMessage)
    MESSAGE_HANDLER(WM_CHAR, OnMessage)
    MESSAGE_RANGE_HANDLER(WM_MOUSEFIRST, WM_MOUSELAST, OnMessage)
  END_MSG_MAP()

  LRESULT OnMessage(UINT, WPARAM, LPARAM, BOOL&)
  { // do nothing

    return 0; }

3. String Table Resources

The following method, from the Help dialog class, is used to load string resources from the executable for display in the edit control. When a user clicks a tab, the TCN_SELCHANGE notify code handler calls SetHelpText(tabID) to display the appropriate help text. Strings were entered into the String Table resource with two \r\n line breaks between paragraphs.

void SetHelpText(int nTabID)
{ TCHAR cHelpText[1000];

  // load the appropriate string table resource

  switch (nTabID) {
  case 0:
  {
    AtlLoadString(IDS_SOFTHELP, cHelpText, 1000);
    break;
  } } // end switch


  // set the edit's text

  m_helpEdit.SetWindowText(cHelpText); }

WTL Release Builds

It did not take us long to encounter the dreaded "LIBCMT.LIB(crt0.obj) : error LNK2001: unresolved external symbol _main". This message occurs when you use templates and functions that require C-RunTime (CRT) startup routines. For example, memchr, strcmp, vector and hundreds of others. Any functions defined as _CRTIMP and templates using such functions will not Release build with default WTL settings.

There are two solutions. You can replace any functions that require CRT. If you read through the WTL source code, you will find the WTL programmers at Microsoft used many substitute functions. For example, lstrcmp is used in place of strcmp, and a number of helpers like CString's _cstrchr were written to replace CRT functions.

Alternatively, you can remove _ATL_MIN_CRT from the preprocessor definitions for the Release project. If you do that, MSVCRTxx.dll must be available to your application at runtime.

Conclusion

We did finally meet our development goals. Smasher! is a self-contained exe, is relatively small (136K, zips to 32K), does not require many external libraries, and has run on every system from Win95 SR1 to Windows XP. It does not run on a fresh install of Win95 "classic" from floppy disk unless the optional Internet Explorer is installed.

We hope you enjoy playing Smasher! and can use some of its techniques in your own programs.

Acknowledgements

Bjarke Viksoe, for the skinned dialog class. Chris Becke, for the bitmap transparency. Andy Williams and Knuth, Mitchell, Moore, for the random number generator. Several CodeProject articles provided ideas and tips.

Terms Of Use

Smasher! is copyrighted free software. MAY NOTS: You may not claim that Smasher! is your creation. You may not modify the copyright or any other notices and redistribute the software or its source code. You may not charge for distribution of the software. MAYS: You may reuse any portion of the source code in your own programs in any form you wish as long as all applicable copyrights are maintained and any usage restrictions of the copyright holders are followed. You may distribute Smasher! in binary form by any means to anybody as long as it is distributed for free.

SMASHER! IS DISTRIBUTED AS-IS, WITHOUT WARRANTIES OF ANY KIND.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here