Sign in to follow this  

[.net] Game Loop in C#?

This topic is 4795 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

There are two ways that I can think of to do it. But the easiest (for me to demonstrate it) is this..


static void Main()
{
using (Form1 frm = new Form1())
{
frm.Show();

while (frm.Created)
{
frm.Render();

Application.DoEvents();
}

}
}

This is of course assuming your form is called Form1.

The other (probably better) way to do it is to wrap some Win32 API functions to create a traditional windows message loop. There are quite a few threads on this forum describing this method. I tend to use the above one when trying stuff out - its a lot quicker and no fuss. The only catch is I believe there are some memory allocations occuring in DoEvents() which can cause the garbage collector to fire more frequently. Again, there is a good thread on this forum regarding this and what exactly occurs in the DoEvents() method.

Share this post


Link to post
Share on other sites

i belive using application.doevents() allocates alot of objects over time. so its not really suited to being used for a game loop i would suggest doing some thing similar to this.


//=============================================
// MessagePump.cs
// a replacement for application.doevents()
//=============================================
#region Using directives

using System;
using System.Reflection;
using System.Runtime.InteropServices;

#endregion

namespace Win32
{
public class MessagePump
{
public enum PeekMessage_Paramaters
{
No_Remove = 0,
Remove = 1,
No_Yield = 2
}

public struct POINT
{
public Int32 X;
public Int32 Y;
}

public struct MSG
{
public IntPtr hwnd;
public uint message;
public uint wParam;
public uint lParam;
public uint time;
public POINT pt;
}

private static MSG msg = new MSG();

[DllImport("user32", EntryPoint = "PeekMessage")]
public static extern bool PeekMessage(out MSG lpMsg,
IntPtr hWnd,
Int32 MsgFilterMin,
Int32 MsgFilterMax,
PeekMessage_Paramaters RemoveMsg);

[DllImport("user32", EntryPoint = "TranslateMessage")]
public static extern bool TranslateMessage(ref MSG
lpMessage);

[DllImport("user32", EntryPoint = "DispatchMessage")]
public static extern Int32 DispatchMessage(ref MSG
lpMessage);

public static bool DoEvents()
{
if (PeekMessage(out msg, (IntPtr)0, 0, 0, PeekMessage_Paramaters.Remove))
{
TranslateMessage(ref msg);
DispatchMessage(ref msg);
return true;
}
return false;
}

public static bool DoEvents(IntPtr hWnd)
{
if (PeekMessage(out msg, hWnd, 0, 0, PeekMessage_Paramaters.Remove))
{
TranslateMessage(ref msg);
DispatchMessage(ref msg);
return true;
}
return false;
}

}
}




// game loop
while (running && gameWindow.Created)
{
while (!MessagePump.DoEvents())
{
GameMain();
}
}



i hope this helps you

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Tom Miller, co-creator of the Managed DX libraries officially denounces any game loop which using DoEvents. Apparently it causes problems with the garbage collector and has the potential for much greater overhead. I believe it also does some dirty stuff to the events model.

The solution he suggests is to hook the OnPaint override to perform rendering, invalidating it when necessary to update the frame. This method also requires you to make the following setting on your form:
this.Setstyle(Controlstyles.AllPaintingInWmPaint | Controlstyles.Opaque, true);

Share this post


Link to post
Share on other sites
You're right Andrew, I will most likely see this forum :). My post about this can be found here. It's important to note that the post is a little out of date though. For performant games, there is another method that Tom Miller actually recommends now instead of the OnPaint method(and RenZimE was basically right on). Tom Miller recommends hooking into the Win32 messaging loop via P/Invoke. As a commenter says on the post above, it almost seems like a step back in my opinion. Oh well. Whoever said writing performant games was easy, right?

If you want to know how this is done, you can simply look at the sample framework used in the various samples with the latest SDK update. The sample framework uses the Win32 methodology.

I hope this helps :).

Share this post


Link to post
Share on other sites
i really dont think it is that much of a step back. you just have to write a few more lines of code, and you only have to do it once. Write your own DoEvents() method like that and forget about it, it will be like good old times ( the DoEvents() with no worries times ).

Share this post


Link to post
Share on other sites
Hi Jason, I was wondering wether you could give a quick shakedown of the 'performant' render loop that tom miller suggested? I'm forced to stick around the 9.0b SDK, so I can't browse the latest samples and these all use DoEvents... :(

Share this post


Link to post
Share on other sites
Hey Martaver, no problem. However, I believe it is a better learning experience to reverse engineer existing code so that you really understand what it is doing rather than having it spoon fed to you. In that vein, here is the source for the main SampleFramework class's main game loop from the 9.0c samples:


public void GameLoop()
{
// Not allowed to call this from inside the device callbacks or reenter
if (State.IsInsideDeviceCallback || State.IsInsideMainloop)
{
State.ApplicationExitCode = 1;
throw new InvalidOperationException("You cannot call this method from a callback, or reenter the method.");
}

// We're inside the loop now
State.IsInsideMainloop = true;

// If CreateDevice*() or SetDevice() has not already been called,
// then call CreateDevice() with the default parameters.
if (!State.WasDeviceCreated)
{
if (State.WasDeviceCreateCalled)
{
// It already called and failed, don't try again
State.ApplicationExitCode = 1;
throw new InvalidOperationException("The device was never created.");
}

try
{
CreateDevice(0, true, 640, 480, null);
}
catch
{
// Well, can't do anything, just set the exit code and rethrow
State.ApplicationExitCode = 1;
throw;
}
}

// Initialize() must have been called and succeeded for this function to proceed
// CreateWindow() or SetWindow() must have been called and succeeded for this function to proceed
// CreateDevice() or CreateDeviceFromSettings() or SetDevice() must have been called and succeeded for this function to proceed
if ( (!State.IsInited) || (!State.WasWindowCreated) || (!State.WasDeviceCreated) )
{
State.ApplicationExitCode = 1;
throw new InvalidOperationException("The framework was not initialized, cannot continue.");
}

bool gotMessage = false;
NativeMethods.Message msg;

// Get the first message
NativeMethods.PeekMessage(out msg, IntPtr.Zero, 0, 0, NativeMethods.PeekMessageFlags.NoRemove);

while(msg.msg != NativeMethods.WindowMessage.Quit)
{
gotMessage = NativeMethods.PeekMessage(out msg, IntPtr.Zero, 0, 0, NativeMethods.PeekMessageFlags.Remove);
if (gotMessage)
{
NativeMethods.TranslateMessage(ref msg);
NativeMethods.DispatchMessage(ref msg);
}
else
{
// Render a frame during idle time (no messages are waiting)
Render3DEnvironment();
}
}
}



and the NativeMethods you see being used above:


public class NativeMethods
{
#region Win32 User Messages / Structures
/// <summary>Show window flags styles</summary>
public enum ShowWindowFlags: uint
{
Hide = 0,
ShowNormal = 1,
Normal = 1,
ShowMinimized = 2,
ShowMaximized = 3,
ShowNoActivate = 4,
Show = 5,
Minimize = 6,
ShowMinNoActivate = 7,
ShowNotActivated = 8,
Restore = 9,
ShowDefault = 10,
ForceMinimize = 11,

}
/// <summary>Window styles</summary>
[Flags]
public enum WindowStyles: uint
{
Overlapped = 0x00000000,
Popup = 0x80000000,
Child = 0x40000000,
Minimize = 0x20000000,
Visible = 0x10000000,
Disabled = 0x08000000,
ClipSiblings = 0x04000000,
ClipChildren = 0x02000000,
Maximize = 0x01000000,
Caption = 0x00C00000, /* WindowStyles.Border | WindowStyles.DialogFrame */
Border = 0x00800000,
DialogFrame = 0x00400000,
VerticalScroll = 0x00200000,
HorizontalScroll = 0x00100000,
SystemMenu = 0x00080000,
ThickFrame = 0x00040000,
Group = 0x00020000,
TabStop = 0x00010000,
MinimizeBox = 0x00020000,
MaximizeBox = 0x00010000,
}

/// <summary>Peek message flags</summary>
public enum PeekMessageFlags : uint
{
NoRemove = 0,
Remove = 1,
NoYield = 2,
}

/// <summary>Window messages</summary>
public enum WindowMessage : uint
{
// Misc messages
Destroy = 0x0002,
Close = 0x0010,
Quit = 0x0012,
Paint = 0x000F,
SetCursor = 0x0020,
ActivateApplication = 0x001C,
EnterMenuLoop = 0x0211,
ExitMenuLoop = 0x0212,
NonClientHitTest = 0x0084,
PowerBroadcast = 0x0218,
SystemCommand = 0x0112,
GetMinMax = 0x0024,

// Keyboard messages
KeyDown = 0x0100,
KeyUp = 0x0101,
Character = 0x0102,
SystemKeyDown = 0x0104,
SystemKeyUp = 0x0105,
SystemCharacter = 0x0106,

// Mouse messages
MouseMove = 0x0200,
LeftButtonDown = 0x0201,
LeftButtonUp = 0x0202,
LeftButtonDoubleClick = 0x0203,
RightButtonDown = 0x0204,
RightButtonUp = 0x0205,
RightButtonDoubleClick = 0x0206,
MiddleButtonDown = 0x0207,
MiddleButtonUp = 0x0208,
MiddleButtonDoubleClick = 0x0209,
MouseWheel = 0x020a,
XButtonDown = 0x020B,
XButtonUp = 0x020c,
XButtonDoubleClick = 0x020d,
MouseFirst = LeftButtonDown, // Skip mouse move, it happens a lot and there is another message for that
MouseLast = XButtonDoubleClick,

// Sizing
EnterSizeMove = 0x0231,
ExitSizeMove = 0x0232,
Size = 0x0005,

}

/// <summary>Mouse buttons</summary>
public enum MouseButtons
{
Left = 0x0001,
Right = 0x0002,
Middle = 0x0010,
Side1 = 0x0020,
Side2 = 0x0040,
}

/// <summary>Windows Message</summary>
[StructLayout(LayoutKind.Sequential)]
public struct Message
{
public IntPtr hWnd;
public WindowMessage msg;
public IntPtr wParam;
public IntPtr lParam;
public uint time;
public System.Drawing.Point p;
}

/// <summary>MinMax Info structure</summary>
[StructLayout(LayoutKind.Sequential)]
public struct MinMaxInformation
{
public System.Drawing.Point reserved;
public System.Drawing.Point MaxSize;
public System.Drawing.Point MaxPosition;
public System.Drawing.Point MinTrackSize;
public System.Drawing.Point MaxTrackSize;
}

/// <summary>Monitor Info structure</summary>
[StructLayout(LayoutKind.Sequential)]
public struct MonitorInformation
{
public uint Size; // Size of this structure
public System.Drawing.Rectangle MonitorRectangle;
public System.Drawing.Rectangle WorkRectangle;
public uint Flags; // Possible flags
}

/// <summary>Window class structure</summary>
[StructLayout(LayoutKind.Sequential)]
public struct WindowClass
{
public int Styles;
[MarshalAs(UnmanagedType.FunctionPtr)] public WndProcDelegate WindowsProc;
private int ExtraClassData;
private int ExtraWindowData;
public IntPtr InstanceHandle;
public IntPtr IconHandle;
public IntPtr CursorHandle;
public IntPtr backgroundBrush;
[MarshalAs(UnmanagedType.LPTStr)] public string MenuName;
[MarshalAs(UnmanagedType.LPTStr)] public string ClassName;
}
#endregion

#region Delegates
public delegate IntPtr WndProcDelegate(IntPtr hWnd, NativeMethods.WindowMessage msg, IntPtr wParam, IntPtr lParam);
#endregion

#region Windows API calls
[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[System.Runtime.InteropServices.DllImport("winmm.dll")]
public static extern IntPtr timeBeginPeriod(uint period);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern bool PeekMessage(out Message msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, PeekMessageFlags flags);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern bool TranslateMessage(ref Message msg);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern bool DispatchMessage(ref Message msg);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr DefWindowProc(IntPtr hWnd, WindowMessage msg, IntPtr wParam, IntPtr lParam);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern void PostQuitMessage(int exitCode);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
#if(_WIN64)
private static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int index, [MarshalAs(UnmanagedType.FunctionPtr)] WndProcDelegate windowCallback);
#else
private static extern IntPtr SetWindowLong(IntPtr hWnd, int index, [MarshalAs(UnmanagedType.FunctionPtr)] WndProcDelegate windowCallback);
#endif

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", EntryPoint="SetWindowLong", CharSet=CharSet.Auto)]
private static extern IntPtr SetWindowLongStyle(IntPtr hWnd, int index, WindowStyles style);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", EntryPoint="GetWindowLong", CharSet=CharSet.Auto)]
private static extern WindowStyles GetWindowLongStyle(IntPtr hWnd, int index);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("kernel32")]
public static extern bool QueryPerformanceFrequency(ref long PerformanceFrequency);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("kernel32")]
public static extern bool QueryPerformanceCounter(ref long PerformanceCount);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern bool GetClientRect(IntPtr hWnd, out System.Drawing.Rectangle rect);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern bool GetWindowRect(IntPtr hWnd, out System.Drawing.Rectangle rect);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndAfter, int x, int y, int w, int h, uint flags);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern bool ScreenToClient(IntPtr hWnd, ref System.Drawing.Point rect);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr SetFocus(IntPtr hWnd);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr GetParent(IntPtr hWnd);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern bool GetMonitorInfo(IntPtr hWnd, ref MonitorInformation info);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr MonitorFromWindow(IntPtr hWnd, uint flags);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern short GetAsyncKeyState(uint key);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr SetCapture(IntPtr handle);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern bool ReleaseCapture();

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern bool ShowWindow(IntPtr hWnd, ShowWindowFlags flags);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern bool SetMenu(IntPtr hWnd, IntPtr menuHandle);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern bool DestroyWindow(IntPtr hWnd);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern bool IsIconic(IntPtr hWnd);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern bool AdjustWindowRect(ref System.Drawing.Rectangle rect, WindowStyles style,
[MarshalAs(UnmanagedType.Bool)]bool menu);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr windowHandle, WindowMessage msg, IntPtr w, IntPtr l);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr RegisterClass(ref WindowClass wndClass);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern bool UnregisterClass([MarshalAs(UnmanagedType.LPTStr)] string className, IntPtr instanceHandle);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", EntryPoint="CreateWindowEx", CharSet=CharSet.Auto)]
public static extern IntPtr CreateWindow(int exStyle, [MarshalAs(UnmanagedType.LPTStr)] string className, [MarshalAs(UnmanagedType.LPTStr)] string windowName,
WindowStyles style, int x, int y, int width, int height, IntPtr parent, IntPtr menuHandle, IntPtr instanceHandle, IntPtr zero);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern int GetCaretBlinkTime();
#endregion

#region Class Methods
private NativeMethods() {} // No creation
/// <summary>Hooks window messages to go through this new callback</summary>
public static void HookWindowsMessages(IntPtr window, WndProcDelegate callback)
{
#if(_WIN64)
SetWindowLongPtr(window, -4, callback);
#else
SetWindowLong(window, -4, callback);
#endif
}
/// <summary>Set new window style</summary>
public static void SetStyle(IntPtr window, WindowStyles newStyle)
{
SetWindowLongStyle(window, -16, newStyle);
}
/// <summary>Get new window style</summary>
public static WindowStyles GetStyle(IntPtr window)
{
return GetWindowLongStyle(window, -16);
}

/// <summary>Returns the low word</summary>
public static short LoWord(uint l)
{
return (short)(l & 0xffff);
}
/// <summary>Returns the high word</summary>
public static short HiWord(uint l)
{
return (short)(l >> 16);
}

/// <summary>Makes two shorts into a long</summary>
public static uint MakeUInt32(short l, short r)
{
return (uint)((l & 0xffff) | ((r & 0xffff) << 16));
}

/// <summary>Is this key down right now</summary>
public static bool IsKeyDown(System.Windows.Forms.Keys key)
{
return (GetAsyncKeyState((int)System.Windows.Forms.Keys.ShiftKey) & 0x8000) != 0;
}
#endregion
}



If this is too much, just let us know and I'm sure someone can break it down for you (if they get here before me, that is :)). I hope this helps! Good luck!

Share this post


Link to post
Share on other sites
Ok.

I've been looking for a while now to find a great way to create a game loop using C#. The examples here gave me some fresh ideas, However, the real problem I'm experiencing is this. I'd like to use the managed DirectX environment in windowed mode (no need to build a new GUI system when you already have one). The loop works fine however when I access a menu, the loop freezes (as if inside the menu control a second loop is created and running). This is the code.

// local variables
int t_start, t_end; /* frame start and end times (in msec) */
int frame_number, loop_number;

form.Show ();
t_end = System.Environment.TickCount;
frame_number = 0;
loop_number = 0;
while (form.Created) {
t_start = System.Environment.TickCount;
if (t_start - t_end > TIME_BETWEEN_FRAMES) {
RenderFrame ();
t_end = System.Environment.TickCount;
frame_number++;
}
MessagePump.DoEvents ();
loop_number++;
Debug.WriteLine (string.Format ("{0} / {1}", frame_number, loop_number));
}

If a menu is opened (either a mainmenu or system menu), both loop_number and frame_number values will freeze.

The alternative, adding an invalidate command in the paint event also has it drawbacks. If many messages are fired the queue sort of gets stocked with non-paint messages and the frame rate will collapse. Looks like I'm stuck here.

Share this post


Link to post
Share on other sites
Here's a console number guess game I wrote a while back which could show you some logic you could use:


using System;
using ConsoleExtension;

namespace NumberGuess
{
/// <summary>
/// Summary description for Class1.
/// </summary>
class NumberGuess
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
//
// TODO: Add code to start application here
//
ConsoleColor.SetForeGroundColor(ConsoleColor.ForeGroundColor.Blue);
Console.WriteLine("Welcome to Number Guess! \n");
StartGameLoop();
}

public static void StartGameLoop()
{
int iInput, iNumber = 0;
string sInput;
bool bGameIsNotOver = true;
bool bPlayAgain = true;
Random randomClass = new System.Random();

iNumber = randomClass.Next();
iNumber = iNumber % 10 + 1;

while(bGameIsNotOver)
{
ConsoleColor.SetForeGroundColor(ConsoleColor.ForeGroundColor.Green);
Console.WriteLine("Please Guess a Number between 1 and 10");

sInput = Console.ReadLine();

try
{
iInput = Int32.Parse(sInput);

if (iInput < 1 || iInput > 10)
{
ConsoleColor.SetForeGroundColor(ConsoleColor.ForeGroundColor.Red);
Console.WriteLine("Number must be between 1 and 10");
}
else
{
if (iInput == iNumber)
{
ConsoleColor.SetForeGroundColor(ConsoleColor.ForeGroundColor.Yellow);
Console.WriteLine("Congratulations!! You have won!\n");
bGameIsNotOver = false;
}
if (iInput < iNumber)
{
ConsoleColor.SetForeGroundColor(ConsoleColor.ForeGroundColor.Yellow);
Console.WriteLine("Too low!\n");
}
if (iInput > iNumber)
{
ConsoleColor.SetForeGroundColor(ConsoleColor.ForeGroundColor.Yellow);
Console.WriteLine("Too high!\n");
}
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}

}
ConsoleColor.SetForeGroundColor(ConsoleColor.ForeGroundColor.Yellow);
Console.WriteLine("Play again? (y/n)\n");

while (bPlayAgain)
{
try
{
sInput = Console.ReadLine();
sInput.ToString();
if (sInput.Equals("y") || sInput.Equals("Y"))
{
StartGameLoop();
bPlayAgain = false;
}
if (sInput.Equals("n") || sInput.Equals("N"))
{
ConsoleColor.SetForeGroundColor(ConsoleColor.ForeGroundColor.Yellow);
Console.WriteLine("GoodBye!");
bPlayAgain = false;
}
else
{
ConsoleColor.SetForeGroundColor(ConsoleColor.ForeGroundColor.Yellow);
Console.WriteLine("I didn't understand.\n");
Console.WriteLine("Play again? (y/n)\n");
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}

}

}
}




Or, with forms, things are obviously a bit more complicated. Here's the main code for the same game using a form with buttons for each number:


using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

namespace WinGuess
{
/// <summary>
/// Summary description for Form1.
/// </summary>
public class frmNumberGuess : System.Windows.Forms.Form
{
//Variable Declaration
public static int iNumber;
public static Random randomClass;
private System.Windows.Forms.Button cmdButtonOne;
private System.Windows.Forms.Button cmdButtonTwo;
private System.Windows.Forms.Button cmdButtonThree;
private System.Windows.Forms.Button cmdButtonFour;
private System.Windows.Forms.Button cmdButtonFive;
private System.Windows.Forms.Button cmdButtonSix;
private System.Windows.Forms.Button cmdButtonSeven;
private System.Windows.Forms.Button cmdButtonEight;
private System.Windows.Forms.Button cmdButtonNine;
private System.Windows.Forms.Button cmdButtonTen;
private System.Windows.Forms.Button cmdCloseButton;
private System.Windows.Forms.TextBox txtResult;
private System.Windows.Forms.Panel pnlButtonPanel;
private System.Windows.Forms.Button cmdPlayAgain;
private System.Windows.Forms.Label lblTitle;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;

public frmNumberGuess()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
iNumber = 0;
randomClass = new System.Random();
StartGame();

//
// TODO: Add any constructor code after InitializeComponent call
//
}

/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}

#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.cmdButtonOne = new System.Windows.Forms.Button();
this.cmdButtonTwo = new System.Windows.Forms.Button();
this.cmdButtonThree = new System.Windows.Forms.Button();
this.cmdButtonFour = new System.Windows.Forms.Button();
this.cmdButtonFive = new System.Windows.Forms.Button();
this.cmdButtonSix = new System.Windows.Forms.Button();
this.cmdButtonSeven = new System.Windows.Forms.Button();
this.cmdButtonEight = new System.Windows.Forms.Button();
this.cmdButtonNine = new System.Windows.Forms.Button();
this.cmdButtonTen = new System.Windows.Forms.Button();
this.cmdCloseButton = new System.Windows.Forms.Button();
this.txtResult = new System.Windows.Forms.TextBox();
this.pnlButtonPanel = new System.Windows.Forms.Panel();
this.cmdPlayAgain = new System.Windows.Forms.Button();
this.lblTitle = new System.Windows.Forms.Label();
this.pnlButtonPanel.SuspendLayout();
this.SuspendLayout();
//
// cmdButtonOne
//
this.cmdButtonOne.Font = new System.Drawing.Font("Microsoft Sans Serif", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
this.cmdButtonOne.Location = new System.Drawing.Point(8, 8);
this.cmdButtonOne.Name = "cmdButtonOne";
this.cmdButtonOne.Size = new System.Drawing.Size(48, 48);
this.cmdButtonOne.TabIndex = 1;
this.cmdButtonOne.Text = "1";
this.cmdButtonOne.Click += new System.EventHandler(this.cmdButtonOne_Click);
//
// cmdButtonTwo
//
this.cmdButtonTwo.Font = new System.Drawing.Font("Microsoft Sans Serif", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
this.cmdButtonTwo.Location = new System.Drawing.Point(64, 8);
this.cmdButtonTwo.Name = "cmdButtonTwo";
this.cmdButtonTwo.Size = new System.Drawing.Size(48, 48);
this.cmdButtonTwo.TabIndex = 2;
this.cmdButtonTwo.Text = "2";
this.cmdButtonTwo.Click += new System.EventHandler(this.cmdButtonTwo_Click);
//
// cmdButtonThree
//
this.cmdButtonThree.Font = new System.Drawing.Font("Microsoft Sans Serif", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
this.cmdButtonThree.Location = new System.Drawing.Point(120, 8);
this.cmdButtonThree.Name = "cmdButtonThree";
this.cmdButtonThree.Size = new System.Drawing.Size(48, 48);
this.cmdButtonThree.TabIndex = 3;
this.cmdButtonThree.Text = "3";
this.cmdButtonThree.Click += new System.EventHandler(this.cmdButtonThree_Click);
//
// cmdButtonFour
//
this.cmdButtonFour.Font = new System.Drawing.Font("Microsoft Sans Serif", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
this.cmdButtonFour.Location = new System.Drawing.Point(176, 8);
this.cmdButtonFour.Name = "cmdButtonFour";
this.cmdButtonFour.Size = new System.Drawing.Size(48, 48);
this.cmdButtonFour.TabIndex = 4;
this.cmdButtonFour.Text = "4";
this.cmdButtonFour.Click += new System.EventHandler(this.cmdButtonFour_Click);
//
// cmdButtonFive
//
this.cmdButtonFive.Font = new System.Drawing.Font("Microsoft Sans Serif", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
this.cmdButtonFive.Location = new System.Drawing.Point(232, 8);
this.cmdButtonFive.Name = "cmdButtonFive";
this.cmdButtonFive.Size = new System.Drawing.Size(48, 48);
this.cmdButtonFive.TabIndex = 5;
this.cmdButtonFive.Text = "5";
this.cmdButtonFive.Click += new System.EventHandler(this.cmdButtonFive_Click);
//
// cmdButtonSix
//
this.cmdButtonSix.Font = new System.Drawing.Font("Microsoft Sans Serif", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
this.cmdButtonSix.Location = new System.Drawing.Point(8, 64);
this.cmdButtonSix.Name = "cmdButtonSix";
this.cmdButtonSix.Size = new System.Drawing.Size(48, 48);
this.cmdButtonSix.TabIndex = 6;
this.cmdButtonSix.Text = "6";
this.cmdButtonSix.Click += new System.EventHandler(this.cmdButtonSix_Click);
//
// cmdButtonSeven
//
this.cmdButtonSeven.Font = new System.Drawing.Font("Microsoft Sans Serif", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
this.cmdButtonSeven.Location = new System.Drawing.Point(64, 64);
this.cmdButtonSeven.Name = "cmdButtonSeven";
this.cmdButtonSeven.Size = new System.Drawing.Size(48, 48);
this.cmdButtonSeven.TabIndex = 7;
this.cmdButtonSeven.Text = "7";
this.cmdButtonSeven.Click += new System.EventHandler(this.cmdButtonSeven_Click);
//
// cmdButtonEight
//
this.cmdButtonEight.Font = new System.Drawing.Font("Microsoft Sans Serif", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
this.cmdButtonEight.Location = new System.Drawing.Point(120, 64);
this.cmdButtonEight.Name = "cmdButtonEight";
this.cmdButtonEight.Size = new System.Drawing.Size(48, 48);
this.cmdButtonEight.TabIndex = 8;
this.cmdButtonEight.Text = "8";
this.cmdButtonEight.Click += new System.EventHandler(this.cmdButtonEight_Click);
//
// cmdButtonNine
//
this.cmdButtonNine.Font = new System.Drawing.Font("Microsoft Sans Serif", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
this.cmdButtonNine.Location = new System.Drawing.Point(176, 64);
this.cmdButtonNine.Name = "cmdButtonNine";
this.cmdButtonNine.Size = new System.Drawing.Size(48, 48);
this.cmdButtonNine.TabIndex = 9;
this.cmdButtonNine.Text = "9";
this.cmdButtonNine.Click += new System.EventHandler(this.cmdButtonNine_Click);
//
// cmdButtonTen
//
this.cmdButtonTen.Font = new System.Drawing.Font("Microsoft Sans Serif", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
this.cmdButtonTen.Location = new System.Drawing.Point(232, 64);
this.cmdButtonTen.Name = "cmdButtonTen";
this.cmdButtonTen.Size = new System.Drawing.Size(48, 48);
this.cmdButtonTen.TabIndex = 10;
this.cmdButtonTen.Text = "10";
this.cmdButtonTen.Click += new System.EventHandler(this.cmdButtonTen_Click);
//
// cmdCloseButton
//
this.cmdCloseButton.Location = new System.Drawing.Point(192, 176);
this.cmdCloseButton.Name = "cmdCloseButton";
this.cmdCloseButton.Size = new System.Drawing.Size(88, 32);
this.cmdCloseButton.TabIndex = 11;
this.cmdCloseButton.Text = "Close";
this.cmdCloseButton.Click += new System.EventHandler(this.cmdCloseButton_Click);
//
// txtResult
//
this.txtResult.Location = new System.Drawing.Point(8, 144);
this.txtResult.Name = "txtResult";
this.txtResult.Size = new System.Drawing.Size(272, 20);
this.txtResult.TabIndex = 12;
this.txtResult.Text = "";
//
// pnlButtonPanel
//
this.pnlButtonPanel.Controls.Add(this.cmdButtonSix);
this.pnlButtonPanel.Controls.Add(this.cmdButtonFive);
this.pnlButtonPanel.Controls.Add(this.cmdButtonSeven);
this.pnlButtonPanel.Controls.Add(this.cmdButtonTen);
this.pnlButtonPanel.Controls.Add(this.cmdButtonOne);
this.pnlButtonPanel.Controls.Add(this.cmdButtonTwo);
this.pnlButtonPanel.Controls.Add(this.cmdButtonFour);
this.pnlButtonPanel.Controls.Add(this.cmdButtonNine);
this.pnlButtonPanel.Controls.Add(this.cmdButtonThree);
this.pnlButtonPanel.Controls.Add(this.cmdButtonEight);
this.pnlButtonPanel.Location = new System.Drawing.Point(0, 24);
this.pnlButtonPanel.Name = "pnlButtonPanel";
this.pnlButtonPanel.Size = new System.Drawing.Size(288, 120);
this.pnlButtonPanel.TabIndex = 13;
//
// cmdPlayAgain
//
this.cmdPlayAgain.Location = new System.Drawing.Point(88, 176);
this.cmdPlayAgain.Name = "cmdPlayAgain";
this.cmdPlayAgain.Size = new System.Drawing.Size(88, 32);
this.cmdPlayAgain.TabIndex = 14;
this.cmdPlayAgain.Text = "Play Again?";
this.cmdPlayAgain.Visible = false;
this.cmdPlayAgain.Click += new System.EventHandler(this.cmdPlayAgain_Click);
//
// lblTitle
//
this.lblTitle.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
this.lblTitle.Location = new System.Drawing.Point(0, 0);
this.lblTitle.Name = "lblTitle";
this.lblTitle.Size = new System.Drawing.Size(288, 24);
this.lblTitle.TabIndex = 15;
this.lblTitle.Text = "Guess A Number!";
this.lblTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// frmNumberGuess
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(288, 214);
this.Controls.Add(this.lblTitle);
this.Controls.Add(this.cmdPlayAgain);
this.Controls.Add(this.pnlButtonPanel);
this.Controls.Add(this.txtResult);
this.Controls.Add(this.cmdCloseButton);
this.Name = "frmNumberGuess";
this.Text = "Number Guess";
this.pnlButtonPanel.ResumeLayout(false);
this.ResumeLayout(false);

}
#endregion

/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new frmNumberGuess());
}

public static void StartGame()
{
iNumber = frmNumberGuess.randomClass.Next();
iNumber = iNumber % 10 + 1;

}

private void cmdButtonOne_Click(object sender, System.EventArgs e)
{
if (iNumber == 1)
{
txtResult.Text = "Congratulations, you've won!";
pnlButtonPanel.Enabled = false;
cmdPlayAgain.Visible = true;
}
else
{
txtResult.Text = "Too low!";
cmdButtonOne.Enabled = false;
}
}

private void cmdButtonTwo_Click(object sender, System.EventArgs e)
{
if (iNumber == 2)
{
txtResult.Text = "Congratulations, you've won!";
pnlButtonPanel.Enabled = false;
cmdPlayAgain.Visible = true;
}
else
{
cmdButtonTwo.Enabled = false;
if (iNumber < 2)
{
txtResult.Text = "Too high!";
}
else
{
txtResult.Text = "Too low!";
}
}
}

private void cmdButtonThree_Click(object sender, System.EventArgs e)
{
if (iNumber == 3)
{
txtResult.Text = "Congratulations, you've won!";
pnlButtonPanel.Enabled = false;
cmdPlayAgain.Visible = true;
}
else
{
cmdButtonThree.Enabled = false;
if (iNumber < 3)
{
txtResult.Text = "Too high!";
}
else
{
txtResult.Text = "Too low!";
}
}
}

private void cmdButtonFour_Click(object sender, System.EventArgs e)
{
if (iNumber == 4)
{
txtResult.Text = "Congratulations, you've won!";
pnlButtonPanel.Enabled = false;
cmdPlayAgain.Visible = true;
}
else
{
cmdButtonFour.Enabled = false;
if (iNumber < 4)
{
txtResult.Text = "Too high!";
}
else
{
txtResult.Text = "Too low!";
}
}
}

private void cmdButtonFive_Click(object sender, System.EventArgs e)
{
if (iNumber == 5)
{
txtResult.Text = "Congratulations, you've won!";
pnlButtonPanel.Enabled = false;
cmdPlayAgain.Visible = true;
}
else
{
cmdButtonFive.Enabled = false;
if (iNumber < 5)
{
txtResult.Text = "Too high!";
}
else
{
txtResult.Text = "Too low!";
}
}
}

private void cmdButtonSix_Click(object sender, System.EventArgs e)
{
if (iNumber == 6)
{
txtResult.Text = "Congratulations, you've won!";
pnlButtonPanel.Enabled = false;
cmdPlayAgain.Visible = true;
}
else
{
cmdButtonSix.Enabled = false;
if (iNumber < 6)
{
txtResult.Text = "Too high!";
}
else
{
txtResult.Text = "Too low!";
}
}
}

private void cmdButtonSeven_Click(object sender, System.EventArgs e)
{
if (iNumber == 7)
{
txtResult.Text = "Congratulations, you've won!";
pnlButtonPanel.Enabled = false;
cmdPlayAgain.Visible = true;
}
else
{
cmdButtonSeven.Enabled = false;
if (iNumber < 7)
{
txtResult.Text = "Too high!";
}
else
{
txtResult.Text = "Too low!";
}
}
}

private void cmdButtonEight_Click(object sender, System.EventArgs e)
{
if (iNumber == 8)
{
txtResult.Text = "Congratulations, you've won!";
pnlButtonPanel.Enabled = false;
cmdPlayAgain.Visible = true;
}
else
{
cmdButtonEight.Enabled = false;
if (iNumber < 8)
{
txtResult.Text = "Too high!";
}
else
{
txtResult.Text = "Too low!";
}
}
}

private void cmdButtonNine_Click(object sender, System.EventArgs e)
{
if (iNumber == 9)
{
txtResult.Text = "Congratulations, you've won!";
pnlButtonPanel.Enabled = false;
cmdPlayAgain.Visible = true;
}
else
{
cmdButtonNine.Enabled = false;
if (iNumber < 9)
{
txtResult.Text = "Too high!";
}
else
{
txtResult.Text = "Too low!";
}
}
}

private void cmdButtonTen_Click(object sender, System.EventArgs e)
{
if (iNumber == 10)
{
txtResult.Text = "Congratulations, you've won!";
pnlButtonPanel.Enabled = false;
cmdPlayAgain.Visible = true;
}
else
{
cmdButtonTen.Enabled = false;
txtResult.Text = "Too high!";
}
}

private void cmdCloseButton_Click(object sender, System.EventArgs e)
{
Application.Exit();
}

private void cmdPlayAgain_Click(object sender, System.EventArgs e)
{
cmdPlayAgain.Visible = false;
pnlButtonPanel.Enabled = true;
txtResult.Text = "New Game Started.";
resetButtons();
StartGame();
}
private void resetButtons()
{
cmdButtonOne.Enabled = true;
cmdButtonTwo.Enabled = true;
cmdButtonThree.Enabled = true;
cmdButtonFour.Enabled = true;
cmdButtonFive.Enabled = true;
cmdButtonSix.Enabled = true;
cmdButtonSeven.Enabled = true;
cmdButtonEight.Enabled = true;
cmdButtonNine.Enabled = true;
cmdButtonTen.Enabled = true;
}

}
}




That should give you some ideas.

Share this post


Link to post
Share on other sites
With regard to the paint/invalidate method mentioned above, after benchmarking this appears to be a very slow method of drawing - possibly negating any benefits gained from not using DoEvents.

In my code at the moment, I use DoEvents and it doesn't appear to suck a particularly large amount of memory - nor do I see a large degradation in performance due to promoting things to gen1 and the garbage collector running more frequently for gen0 etc.

Of course, using the Peek/GetMessage way is the fastest, but as another poster mentioned this seems like a step backwards.

Also - will these old win32 functions be supported under native longhorn applications? If not, and you can get good enough performance from DoEvents, then why not use it?

Share this post


Link to post
Share on other sites
I've been recently encountering a similar issue. An application that I've been working on uses the Application.DoEvents call. DoEvents() raises the following error after several hours:

An unhandled exception of type 'System.ArgumentException' occurred in mscorlib.dll
Additional information: Item has already been added. Key in dictionary: "-1" Key being added: "-1"

Unfortunately this error occurs after approximately 9 hours of running time. The application usually has just been idly running on the machine. (We're into a deep testing phase and this issue just came up out of the blue.)

The callstack at crashtime is equally unhelpful.
HashTable.Insert
HashTable.Add
ComponentManager.System.Windows.Forms.UnsafeMethods + IMsoComponenetManager.FRegisterComponent
ThreadContext.get_ComponentManager
ThreadContext.RunMessageLoopInner
ThreadContext.RunMessageLoop
Application.DoEvents
Mainform.Main

Application.Run doesn't seem to have this problem. The application has a rendering window that the user can interact with and several Windows Forms that provide the UI.

I was just wondering if anyone had a similar experience and could help give me some insight.

I'm about to try the new DX9 sdk way of doing things, but like any good software engineer, I'd like to know what the problem is before jumping off onto another code path. Any thoughts?

Share this post


Link to post
Share on other sites

This topic is 4795 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this