Click here to Skip to main content
15,885,985 members
Articles / Mobile Apps

Nerdkill game for PocketPC

Rate me:
Please Sign up or sign in to vote.
4.46/5 (29 votes)
28 Jun 2004CPOL13 min read 101.7K   1.2K   35  
A shoot'em up platform in C# for the .NET Compact Framework.
<!--------------------------------------------------------------------------->  
<!--                           INTRODUCTION                                

 The Code Project article submission template (HTML version)

Using this template will help us post your article sooner. To use, just 
follow the 3 easy steps below:
 
     1. Fill in the article description details
     2. Add links to your images and downloads
     3. Include the main article text

That's all there is to it! All formatting will be done by our submission
scripts and style sheets. 

-->  
<!--------------------------------------------------------------------------->  
<!--                        IGNORE THIS SECTION                            -->
<html>
<head>
<title>Nerdill game for PocketPC</title>
<Style>
BODY, P, TD { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 10pt }
H2,H3,H4,H5 { color: #ff9900; font-weight: bold; }
H2 { font-size: 13pt; }
H3 { font-size: 12pt; }
H4 { font-size: 10pt; color: black; }
PRE { BACKGROUND-COLOR: #FBEDBB; FONT-FAMILY: "Courier New", Courier, mono; WHITE-SPACE: pre; }
CODE { COLOR: #990000; FONT-FAMILY: "Courier New", Courier, mono; }
</style>
<link rel="stylesheet" type=text/css href="http://www.codeproject.com/styles/global.css">
</head>
<body bgcolor="#FFFFFF" color=#000000>
<!--------------------------------------------------------------------------->
<!-------------------------------     STEP 1      --------------------------->
<!--  Fill in the details (CodeProject will reformat this section for you) -->
<pre>
Title:       Nerdill game for Pocket PC
Author:      Raphael MOLL 
Email:       ralf-nerdkill@alfray.com or ralfoide@yahoo.com
Environment: Visual Studio 2003, .Net Compact Framework
Keywords:    Control, Dialog, MFC
Level:       Intermediate&quot;
Description: A shot'em up platform in C# for the .Net Compact Framework
Section      .Net Compact Framework
SubSection   Games</pre>
<!-------------------------------     STEP 2      --------------------------->
<!--  Include download and sample image information.                       -->
<ul class=download>
  <li><a href="NerdkillPocket_v100_ARM.zip">Download demo project for ARM - 405 
    Kb </a></li>
  <li><a href="NerdkillPocket_v100_MIPS.zip">Download demo project for MIPS- 405 
    Kb </a></li>
  <li><a href="NerdkillPocket_Src_v100.zip"></a><a href="NerdkillPocket_v100_x86.zip">Download 
    demo project for x86 - 405 Kb </a></li>
  <li><a href="NerdkillPocket_Src_v100.zip"></a><a href="NerdkillPocket_Src_v100.zip">Download 
    source - 472 Kb</a></li>
</ul>

<p><img src="nerdkill_pocket.gif" alt="Sample Image - maximum width is 600 pixels" width=341 height=485></p>


<!-------------------------------     STEP 3      --------------------------->
<!--  Add the article text. Please use simple formatting (<h2>, <p> etc)   --> 

<h2>Introduction</h2>

<p><a href="http://www.alfray.com/projects/Nerdkill/">Nerdkill</a> is a simple 
  2D stress-relieving game that involves targeting little nerds that scamper across 
  the screen. I first wrote this game for the BeOS several years ago and last 
  year I rewrote it in C# for .Net and DirectX Managed, mostly as a simple exercise 
  to learn the DirectX Managed APIs. 
<p>The article describes the effort of porting a simple 2D game with sound to the 
  Pocket PC, possibly as a 100% Managed application running under .Net Compact 
  Framework, and exploring the limitations imposed by the device and framework 
  capabilities.
<p>The resulting fully functional game and full source code are provided under 
  a GPL license.<br>
  This game was tested on the Pocket PC simulator as well as on a Cassiopeia E-125 
  (MIPS 140 MHz.) 
<h2>Underlying Architecture</h2>
<p>At the heart of <a href="http://www.alfray.com/projects/Nerdkill/">Nerdkill 
  C#</a> (the full desktop version, available at <a href="http://www.alfray.com/projects/Nerdkill/">http://www.alfray.com/projects/Nerdkill/</a>) 
  is a reusable game framework that defines a simple architecture to host a game 
  application. The architecture separates the game engine from the platform display, 
  input, sound and resource management by defining the following components:</p>

<pre>public interface Engine.IEngineProcess;
public interface Engine.IEngine2D;
public interface Engine.IEngineSound;
public interface Engine.IEngineInput;
public interface Engine.IEngineResources;</pre>
The <code>IEngineProcess</code> interface is used to implement the game specific 
engine. It processes events generated by the input component, updates its internal 
states, generates asynchronous requests to play sounds and eventually refreshes 
the display by blitting sprites. 
<p>All the other interfaces form auxiliary modules which render services for 
  the engine. They abstract the engine from the actual implementation of the resource 
  handling.</p>
<p>In the case of the DirectX Managed version of <a href="http://www.alfray.com/projects/Nerdkill/">Nerdkill 
  C#</a>, <code>IEngine2D</code> is implemented as a windowed DirectX surface 
  embedded in a Windows.Form, <code>IEngineInput</code> is implemented using mouse 
  and keyboard events generated by the Windows.Form, <code>IEngineSound</code> 
  is implemented using DirectSound and <code>IEngineResources</code> is implemented 
  accessing the data files embedded in the assembly.</p>
<h2>Adapting to Pocket PC</h2>
<p>The architecture used by Nerdkill is pretty flexible. To port the game to Pocket 
  PC, it was merely necessary to reimplement the <code>IEngine2D</code> and <code>IEngineSound</code> 
  interfaces.</p>
<p>The goal was initially to see if the combination of Pocket PC and the .Net 
  Compact Framework was good enough for the two tasks at hand. Ideally the code 
  should be 100% Managed and .Net. In reality, it is all managed yet some parts 
  like the sound left me no choice but access the WinCE APIs through P/Invoke.</p>
<p>Part of the port effort, which I did not exactly expect at first, was adapting 
  the game to the limited resources of the Pocket PC. Obviously every bitmap artwork 
  had to be &quot;shrunk&quot; down to fit on the 240x320 screen and the sounds 
  were sampled down to 11 kHz/8 bits/mono WAV files to reduce the size of the 
  resources. The resulting assembly is 520 kB whereas the original desktop assembly 
  is a mere 2.5 MB.</p>
<p>Luckily most of the gameplay is exactly the same. However, there are a couple 
  of differences. For example on the desktop game, scrolling happens automatically 
  when the mouse approaches the border of the screen. Since there is no MouseOver 
  event with a stylus, I simply use the navigation pad of the Pocket PC instead. 
  A menu that allows to quickly pause the game or disable the sound has been added, 
  and most important it was necessary to automatically pause the game when the 
  application looses the focus, otherwise the game would continue to update when 
  in background, rendering the device extremely slow.</p>
<h2>2D Rendering</h2>
<p>Since the initial goal was a 100% .Net Managed approach to understand the limitations 
  of this framework, the options for rendering 2D bitmaps were rather limited. 
  The requirements for 2D rendering are simple: 
<ul>
  <li>The 2D platform works by blitting sprites.</li>
  <li>Sprites are either fully opaque or have a single transparency color.</li>
  <li>Sprites are loaded directly from the bitmap stored in resources. Several 
    small sprites can be packed in one bitmap.</li>
</ul>
<p>I did not want to use a proprietary library such as GAPI, despite the obvious 
  gain in performances it would give. I was initially tempted to reuse the classic 
  Win32 approach with <code>BitBlt</code> and co. The bitmap manipulation capabilities 
  of .Net are pretty limited, especially for the Compact Framework. Nevertheless 
  it contains everything needed for the purpose here. Bitmaps can be loaded from 
  resources. The Compact Framework does not allow direct access to the bits of 
  the bitmaps except using <code>GetPixel</code> and <code>SetPixel</code>, which 
  are clearly too slow to be acceptable. On the other hand, a <code>Bitmap</code> 
  object can be used to create a <code>Graphics</code> context for GDI+, allowing 
  C# code to simply draw in the offscreen bitmap. 
<p>The article <a href="http://www.codeguru.com/Csharp/Csharp/cs_graphics/drawing/article.php/c6137/">Flicker 
  Free Drawing In C#</a> explains the usual <code>DllImport</code> trick to access 
  the GDI+ functions which are not available directly in .Net: <code>CreateCompatibleDC</code>, 
  <code>CreateCompatibleBitmap</code>, <code>BitBlt</code>, etc. But more important, 
  it shows that most of what is needed is present in .Net Compact Framework. The 
  trick is that a bitmap loaded from resources will not be compatible (thus very 
  slow to draw) but it can be made compatible by creating a new empty bitmap (automagically 
  made compatible), getting a <code>Graphics</code> DC from it (thus a compatible 
  DC) and then using <code>DrawImage</code> to draw the independent bitmap into 
  the compatible one. 
<p> Remember that it is mandatory to release any <code>Bitmap</code> and <code>Graphics</code> 
  DC objects by calling their <code>Dispose</code> method. Failure to do so will 
  cripple the application's available resources. It may not be apparent on a desktop 
  version of Windows but it will be obvious on a Pocket PC when the limited resources 
  get exhausted. 
<p>This code uses the .Net Compact Framework to load a bitmap from the assembly 
  resources and make it compatible:
<pre>private Bitmap loadCompatibleBitmap(string filename)
{
  System.Type st = this.GetType();
  Assembly assembly = st.Assembly;
  Stream stream = assembly.GetManifestResourceStream(st.Namespace + &quot;.&quot; + filename);
   
  // Get the independent bitmap from resources
  Bitmap bitmap = new Bitmap(stream);
  stream.Close();
   
  // Extract the transparency color from the upper left corner (a sensible common hack)
  // Note: for the full .Net Framework, specify the current PixelFormat in the Bitmap constructor too.
  Color bg_col = bitmap.GetPixel(0,0); 
  Bitmap compatible = new Bitmap(bitmap.Size.Width, bitmap.Size.Height);
  Graphics g = Graphics.FromImage(compatible);
   
  // Make sure the offscreen bitmap gets erased with the default
  // background color. Here Black is chosen as transparency color.
  g.Clear(Color.Black);
   
  // Set the color key to what the image had...
  ImageAttributes ia = new ImageAttributes();
  ia.SetColorKey(bg_col, bg_col);
  g.DrawImage(bitmap,
              new Rectangle(0, 0, compatible.Size.Width, compatible.Size.Height),
              0, 0, 
              bitmap.Size.Width, bitmap.Size.Height,
              GraphicsUnit.Pixel, ia);
   
  g.Dispose();
  bitmap.Dispose();
   
  return compatible;
}</pre>
<p>To deal with transparent images, create an <code>ImageAttribute</code> instance, 
  set the transparency color using <code>ImageAttribute.SetColorKey</code> and 
  use the <code>Graphics.DrawImage</code> method that accept an <code>ImageAttribute</code>. 
  The sample code above, the transparency color of the compatible bitmap is set 
  to black. The actual color will generally depend on your artwork.
<p>The 2D rendering implementation for <a href="http://www.alfray.com/projects/Nerdkill/">Nerdkill 
  Pocket</a> uses the same principle everywhere: 
<ul>
  <li>Some bitmaps loaded from resources contain several sprites. For each sprite 
    a new compatible bitmap is created, a <code>Graphics</code> context is created 
    and the sprite is drawn from the independent bitmap onto the compatible one. 
    Note that the .Net Compact Framework does not provide the Bitmap constructor 
    that can extract part of another Bitmap so this workaround is necessary.</li>
  <li>All rendering is done in an offscreen buffer. Then that buffer is drawn in 
    <code>OnPaint</code> directly in the provided <code>Graphics</code> context. 
    This avoids flickering.</li>
</ul>
<p>Note that drawing the compatible bitmap manually in <code>OnPaint</code> is 
  actually pretty fast. A sample code that does just that achieved up to 200 fps 
  on the simulator and up to 100 fps on my test machine, a Cassiopeia E-125.</p>
<p>The complete source of the 2D Rendering part is available in the source archive. 
  It is implemented in the RGdiGraphics.cs file.</p>
<h2>Sound</h2>
<p>I could not find a 100% .Net Managed way to play sounds for the game.<br>
  Instead I found two solutions, both using P/Invoke to access WinCE APIs: 
<ul>
  <li>The <code>PlaySound</code> function.</li>
  <li>The WaveOut API.</li>
</ul>
<p>The <code>PlaySound</code> function is fairly straightforward to use but it 
  can only play one sound at a time. It can play asynchronously. By default it 
  will stop the currently playing sound before starting the next one. There's 
  a flag to avoid that, yet the result is that the new sound will simply not play. 
  It doesn't mix. A sound can loop too and will stop when the next sound is requested.</p>
<p>In the context of this game, a better sound API is required. Several sounds 
  should be able to play simultaneously. Some sounds need to loop automatically. 
  Clearly, reimplementing my own sound mixer using WaveOut was necessary.</p>
<p>WaveOut has a simple but efficient workflow. Buffers first need to be constructed 
  and prepared. They are then filled with data and output using <code>waveOutWrite</code>. 
  Once a buffer has been used, it is returned to the application which can then 
  fill it again and output it.</p>
<p>The implementation is composed of the following classes:</p>

<pre>public class RSoundPlayer: Engine.IEngineSound;
public class RISoundReader: IDisposable;
public class RWavStreamReader: RISoundReader;
public class RWaveOut;
public class RMemAlloc;</pre>

<p><code>RWaveOut</code> maps the various WaveOut methods and structures using 
  <code>DllImport</code>. <code>RMemAlloc</code> does the same for <code>LocalAlloc</code> 
  and <code>LocalFree</code> which are used to allocate the WaveOut buffers.</p>
<p>The sound mixer does not access any sound resource directly. It uses the <code>RISoundReader</code> 
  interface that knows how to read a new buffer of data. The actual reader is 
  implemented by <code>RWavStreamReader</code> and is constructed from a <code>Stream</code> 
  extracted directly from the assembly resources. Since memory is at a premium 
  on Pocket PC, it is neither necessary nor useful to read the full assembly resource 
  stream into a memory buffer. The data can be accessed directly from the resource 
  stream.</p>
<p>The sound data is expected to be formatted as WAV files, mono, 8 bits, 11.025 
  kHz. The stream reader validates the WAV file header to ensure these properties.</p>
<p>Using the WaveOut API is pretty simple: memory buffers are created and &quot;prepared&quot; 
  using <code>waveOutPrepareHeader</code>; they are then filled with data and 
  set to play using <code>waveOutWrite</code>. When the WaveOut interface is done 
  with each buffer, it sends a message to a HWND. The window callback recycles 
  the buffer. The .Net Compact Framework does not allow access to the underlying 
  implementation of a Windows.Form, that is its HWND cannot be retrieved and its 
  WndProc callback cannot be used. To circumvent this limitation, the WinCE's 
  .Net-specific class <code>Microsoft.WindowsCE.Forms.MessageWindow</code> is 
  used:</p>
<pre>private class RWaveOutMsgWindow: MessageWindow
{
  public delegate void Callback(IntPtr waveHdrPtr);

  public void SetBufferDoneCallback(Callback cb)
  {
    mBufferDoneCallback = cb;
  }

  protected override void WndProc(ref Message m)
  {
    if (m.Msg == RWaveOut.MM_WOM_DONE &amp;&amp; mBufferDoneCallback != null)
      mBufferDoneCallback(m.LParam);
    base.WndProc(ref m);
  }

  private Callback mBufferDoneCallback = null;
}

public RSoundPlayer()
{
  ...
  mWceMessageWindow = new RWaveOutMsgWindow();
  mWceMessageWindow.SetBufferDoneCallback(
    new RWaveOutMsgWindow.Callback(this.waveOutBufferDone));
  ...
}
</pre>
<p>The sound mixer maintains a list of sounds that are currently being played. 
  For each of these sounds, a structure holds the current read position, the total 
  size, a stop flag and a repeat flag. The sound mixer also maintains a queue 
  of available WaveOut buffers.</p>
<p>The actual mixer code runs in a thread with the following workflow:</p>
<ul>
  <li> An event is used to signal the thread that new sounds are ready to play 
    or that buffers previously played are now available. If there are no sounds 
    to play nor available buffers to output, the thread just blocks on the event:</li>
</ul>
<pre>public RSoundPlayer()
{
  // create non-signaled (blocking) event
  mMixerEvent = new AutoResetEvent(false);
  // start the mixer thread
  mMixerThread = new Thread(new ThreadStart(this.mixerLoop));
  mMixerThread.Start();
}

private void addSound(RISoundReader reader)
{
  lock(mPlayingSounds.SyncRoot)
  {
    RSoundData sd = new RSoundData(reader);
    mPlayingSounds.Add(sd);
  }
  // signal the mixer another buffer can be processed
  mMixerEvent.Set();
}

private void mixerLoop()
{
  // wait on the event signal...
  while (mMixerEvent != null &amp;&amp; mMixerEvent.WaitOne())
  ...
}</pre>
<ul>
  <li>The thread loop takes the first available buffer and merges in the next 
    incoming samples from every sound. Mixing several sounds is just a matter 
    of adding them to the buffer. Each sample must be added to the one already 
    in place and clipping must be done, as shown below.</li>
  <li>The new buffer is set to play using <code>waveOutWrite</code>.</li>
</ul>
<p>Mixing the actual data in the buffers is done using <i>unsafe</i> C# code (the 
  <i>unsafe</i> keyword allows C# code to manipulate pointers and use pointer 
  arithmetic). Here is a simplified version of the method <code>mixBuffer</code> 
  from RSoundPlayer.cs (the full version is available in the source archive):</p>
<pre>int max_data = 0;
unsafe
{
  int *header = (int *) waveHdr.ToPointer();
  // get the address of the buffer and its size
  uint *dest32 = (uint *)(header[0]);
  int size = mWaveBufferSize / 4;
  // initialize the buffer with 0x80 bytes, *not* zeroes!
  // wave data is from -128 to +127 with a middle point offset at +128.
  while(size-- &gt; 0)
    *(dest32++) = 0x80808080;
}

// accumulate all currently playing sounds in the wave out buffer
lock(mPlayingSounds.SyncRoot)
{
  for(int i = mPlayingSounds.Count-1; i &gt;= 0; i--)
  {
    RSoundData sd = mPlayingSounds[i] as RSoundData;
    int read_size = sd.mSize - sd.mOffset;
    if (read_size &gt; kReadBufferSize)
      read_size = kReadBufferSize;

    // Read a block from the sound stream
    sd.mReader.Read(mMixerBuffer, sd.mOffset, read_size);
    sd.mOffset += read_size;

    unsafe
    {
      unchecked
      {
        fixed(byte *source = mMixerBuffer)
        {
          int *header = (int *) waveHdr.ToPointer();
          byte *dest = (byte *) (header[0]);
          int rsn = read_size * mSampleSize;
          if (rsn &gt; max_data)
            max_data = rsn;

          byte *src = (byte *)source;
          for (int c = read_size; c &gt; 0; c--)
          {
            // Each byte is 0..255 but it really represents
            // a sample which is -128..+127.
            // So the real operation here is:
            // dest = 128 + (dest-128) + (src-128);
            // which is:
            // dest += src-128;
            // then 0..255 clipping must be done.
            int a = (int)(*dest) + (int)(*(src)++) - 0x80;
            if (a &lt; 0)
              a = 0;
            else if (a &gt; 0xFF)
              a = 0xFF;
            byte b = (byte)a;
            for(int j = mSampleSize; j &gt; 0; j--)
              *(dest++) = b;
          }
        } // fixed
      } // unchecked
    } // unsafe

    // end reached?
    if (sd.mOffset &gt;= sd.mSize)
    {
      if (sd.mRepeat)
        sd.mOffset = 0;
      else
        // remove buffer from list
        mPlayingSounds.RemoveAt(i);
    }
  } // for mPlayingSounds
} // lock mPlayingSounds

// Update the size really used in the buffer
unsafe
{
  int *header = (int *) waveHdr.ToPointer();
  // reset some members of a WaveHdr:
  // set the dwBytesRecorded field to the number of actual bytes
  header[2] = max_data; // public uint ;
  // set the dwFlags field... only clear WHDR_DONE here
  header[4] = header[4] &amp; (~RWaveOut.WHDR_DONE);
}</pre>
<p>The mixer is only targeted towards 8-bits output yet it can output to 11.025, 
  22.050 or 44 kHz streams, mono or stereo. This is done by expanding each byte 
  of input as many bytes as necessary to fit in one output sample (no audio filtering 
  is performed). This way the mixer code can stay very simple yet be reasonably 
  adaptive.</p>
<p>In the code, note the usage of the C#-specific keyword <i>fixed</i>. This allows 
  the code to retrieve a pointer on a Managed buffer. When doing this, .Net &quot;pins 
  down&quot; the pointed object so that the Garbage Collector will not move it 
  around. Operations on the pointer can be done as in C/C++ using the familiar 
  &quot;*(dest++)&quot; syntax.</p>
<p>An important note is that since the mixer uses <i>unsafe</i> code, the assembly 
  must be compiled as such. Under Visual Studio, this is done by setting the project's 
  Configuration Property &gt; Build &gt; Allow Unsafe Code Blocks to True. This 
  also means that special rights are necessary to run such an assembly. By default 
  an application running from a desktop or a palm device has such rights. It wouldn't 
  be the case if it was run as a Smart Client or in non-trusted sand box.</p>
<p>In the two previous blocks of code and throughout the RSoundPlayer.cs source, 
  you can also notice the C# keyword <i>lock</i>. This primitive locks a Managed 
  object for exclusive access (mutex). This is used here to synchronize access 
  to the <code>mPlayingSound</code> <code>ArrayList</code> which holds the current 
  list of sounds being played. The mixer thread reads from this list whilst the 
  application thread appends new sound requests to it.</p>
<h2>Conclusion</h2>
<p>The .Net Compact Framework is a suitable candidate for games on the Pocket 
  PC platform.
<p>The main advantage is of course the CLR: compile it once and run it everywhere. 
  Once the .Net Compact Runtime has been installed on the device, the assembly 
  can be deployed no matter what the underlying architecture is.
<p>Draw backs exists though. Blitting full-screen graphics in a Windows.Forms 
  and performing offscreen rendering using solely the Managed interface to GDI+ 
  is not exactly fast. It is barely usable. The game presented here does not try 
  to optimize the rendering to its extreme, which I believe would come at the 
  expense of a simple and somehow generic framework. As it is, the current engine 
  is capped to 10 fps max. It can achieve this frame rate on the simulator but 
  I never saw it go past 4 or 5 fps on a Cassiopeia E-125. By disabling part of 
  the core rendering loop, it is easy to notice that the more small sprites need 
  to be blitted in the offscreen bitmap, the slower the game gets. These graphic 
  updates could probably be made a bit faster by analyzing the needs of the current 
  game and reducing screen updates to the minimum. 
<p>The other obvious limitation of the .Net Compact Framework is the complete 
  absence of sound support. This is not surprising since .Net focus is obviously 
  on building applications relying on Windows.Form. Nevertheless it is a bit disappointing 
  that not even WinCE's <code>PlaySound</code> made it to a Managed class available 
  by default. Since this would not address the needs of a game, it is not a major 
  issue here. 
<p>Deployment is another issue. Visual Studio 7.1 has the ability to generate 
  CAB files. It generates 5 of them, for different architectures. This seems at 
  first surprising since one of the main points of using .Net is to have a single 
  platform-agnostic executable. The reality is that the CLR needs the .Net Compact 
  Framework to be installed and the generated CAB files contain a little native 
  DLL that will check if this is the case. So in effect, unless one assumes the 
  framework is installed, it is necessary to have one CAB per targeted architecture. 
  Fortunately, it seems reasonable to limit ourselves to ARM (for all recent Pocket 
  PCs), MIPS (for older Pocket PCs) and maybe x86 (for the simulator).
<p>The underlying framework presented here can be reused for other applications. 
  It is made available freely under a GPL license. I tried as much as possible 
  to dissociate the game logic from the framework, in the hope that it would be 
  easy to reuse. <br>
  As noted before, the frame per second speed of the current game is rather low. 
  This is due to the code being written as generic and as modular as possible 
  for the purpose of this article. In real life, you may want to introduce a phase 
  of optimization -- analyze speed bottlenecks and rewrite part of the core/game 
  loop or maybe use some unsafe code to perform internal bitblits for example. 
  This is left as an exercise for the reader :-)
<p>Finally, I'd like to thank Mathias for allowing me to use his Cassiopeia E-125 
  for testing. Since it is powered &quot;only&quot; by a 140 Mhz MIPS processor, 
  it is by far not the fastest Pocket PC currently available, which makes it great 
  to see performances issues first hand. I haven't had a chance to run the game 
  on a recent ARM-powered Pocket PC.
<h2>History</h2>

<p>2004/05/27: Version 1.0 of <a href="http://www.alfray.com/projects/Nerdkill/">Nerdkill 
  Pocket</a> and <a href="http://www.codeproject.com/netcf/cfgamenerdkill.asp">original 
  article</a>. <br>
  2004/06/27: Article update (typos, etc.). Splitted demo project in per-architecture 
  downloads.
<p>This page is also mirrored on the <a href="http://www.alfray.com/projects/Nerdkill/">Nerdkill</a> 
  home page: <a href="http://www.alfray.com/projects/Nerdkill/">http://www.alfray.com/projects/Nerdkill/</a>.
<p>
  <!-------------------------------    That's it!   --------------------------->
</body>
</html>

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
United States United States
Raphael is a senior software engineer with a background in electronics engineering.
He enjoys programming in C++ since 1994.
He developed professional software for the BeOS and now focuses on Windows, MacOS and Linux software development.
He uses C++, C#, Java,VB.Net, PHP, Bash and Perl on a regular basis. He is familiar with C, Objective-C, VB6, Python, ML, Haskell, Lisp, Scheme, some obsolete languages (Basic and Pascal) and x86/Motorola assembly languages.
Raphael is a big fan of the .Net platform.
A number of open source personal projects can be found on his web site.

Comments and Discussions