home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue55 / Splat / main.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  2000-01-26  |  7.8 KB  |  273 lines

  1. unit Main;
  2.  
  3. interface
  4.  
  5. // Splat.
  6. // Silly little game that displays a shape and plays a sound when
  7. // the user presses any key or mouse button.
  8. //
  9. // Copyright ⌐ 2000 Tempest Software, Inc.
  10.  
  11. uses
  12.   Windows, Messages, SysUtils, Classes, Graphics, Controls,
  13.   Forms, Dialogs, AppEvnts, ExtCtrls, Shapes;
  14.  
  15. type
  16.   TMainForm = class(TForm)
  17.     AppEvents: TApplicationEvents;
  18.     Image: TImage;
  19.     Timer: TTimer;
  20.     procedure AppEventsMessage(var Msg: tagMSG; var Handled: Boolean);
  21.     procedure FormCreate(Sender: TObject);
  22.     procedure FormDestroy(Sender: TObject);
  23.     procedure TimerTimer(Sender: TObject);
  24.     procedure ImageMouseDown(Sender: TObject; Button: TMouseButton;
  25.       Shift: TShiftState; X, Y: Integer);
  26.     procedure AppEventsDeactivate(Sender: TObject);
  27.   private
  28.     { Private declarations }
  29.     DoubleBuffer: TBitmap;
  30.     ShapeList: TShapeList;
  31.     WaveList: TStringList;
  32.     procedure CreateShape(X: Integer = -1; Y: Integer = -1);
  33.     procedure HandleKeyDown(KeyCode: Word);
  34.     procedure PlayWave(const Name: string);
  35.     procedure PlayRandomWave;
  36.     procedure RedrawShapes;
  37.   public
  38.     { Public declarations }
  39.   end;
  40.  
  41. var
  42.   MainForm: TMainForm;
  43.  
  44. implementation
  45.  
  46. uses MMSystem, Types, KeyText;
  47.  
  48. {$R *.DFM}
  49.  
  50. // Return True if running under control of the Delphi debugger.
  51. function IsDebuggerPresent: Boolean;
  52. begin
  53.   Result := DebugHook <> 0;
  54. end;
  55.  
  56. // Windows 98 does not let one thread steal the keyboard focus from
  57. // another thread. Ordinarily, this is a good thing, but Splat is special.
  58. // The workaround (read "hack") is to attach the current thread to the input
  59. // the foreground thread, then set the foreground window. I learned this
  60. // trick from Karl Peterson's web site:
  61. // http://www.mvps.org/vb/samples.htm
  62. function ForceForegroundWindow(Handle: HWND): Boolean;
  63. var
  64.   Foreground: HWND;
  65.   ForegroundThreadID, ThisThreadID: DWORD;
  66. begin
  67.   Foreground := GetForegroundWindow;
  68.   if Foreground = Handle then
  69.     Result := True
  70.   else
  71.   begin
  72.     ForegroundThreadID := GetWindowThreadProcessId(Foreground, nil);
  73.     ThisThreadID := GetWindowThreadProcessId(Handle, nil);
  74.  
  75.     AttachThreadInput(ThisThreadID, ForegroundThreadID, True);
  76.     SetForegroundWindow(Handle);
  77.     AttachThreadInput(ThisThreadID, ForegroundThreadID, False);
  78.  
  79.     // Return True if the trick worked.
  80.     Result := GetForegroundWindow = Handle;
  81.   end;
  82. end;
  83.  
  84. // Return a string for a resource name or identifier. A resource name
  85. // can be a string or a numeric identifier. Convert a numeric
  86. // identifier to a string as a hexadecimal constant (e.g., $12A).
  87. // The dollar sign makes it easy to convert back to a number and
  88. // to distinguish a numeric ID from a string name.
  89. function ResIDToString(ResName: PChar): string;
  90. begin
  91.   if LongRec(ResName).Hi = 0 then
  92.     Result := Format('$%x', [Integer(ResName)])
  93.   else
  94.     Result := ResName;
  95. end;
  96.  
  97. // Convert a string back to a resource identifier.
  98. function StringToResID(const ResText: string): PChar;
  99. var
  100.   ID: Word;
  101. begin
  102.   if (ResText = '') or (ResText[1] <> '$') then
  103.     Result := PChar(ResText)
  104.   else
  105.   begin
  106.     ID := StrToInt(ResText);  // Make sure the ID is within the proper bounds.
  107.     Result := PChar(ID);
  108.   end;
  109. end;
  110.  
  111. // Collect a list of all WAVE resources, to pick a random one to play
  112. // when the user clicks the mouse.
  113. function EnumWaves(hInstance: THandle; const ResType, ResName: PChar;
  114.     Strings: TStrings): LongBool; stdcall;
  115. begin
  116.   Strings.Add(ResIDToString(ResName));
  117.   Result := True;
  118. end;
  119.  
  120. procedure TMainForm.FormCreate(Sender: TObject);
  121. begin
  122.   // Do not randomize when debugging, so the behavior is predictable.
  123.   if not IsDebuggerPresent then
  124.     Randomize;
  125.  
  126.   // Get a list of all the sound resources, so they can be played back randomly.
  127.   WaveList := TStringList.Create;
  128.   EnumResourceNames(hInstance, 'WAVE', @EnumWaves, LParam(WaveList));
  129.  
  130.   // Create a bitmap to double-buffer the main TImage.
  131.   DoubleBuffer := TBitmap.Create;
  132.  
  133.   if not IsDebuggerPresent then
  134.   begin
  135.     // Make this window the cover the full screen
  136.     // and be topmost of all windows in all applications.
  137.     SetBounds(0, 0, Screen.Width, Screen.Height);
  138.     Win32Check(SetWindowPos(Handle, Hwnd_TopMost, 0, 0, 0, 0, Swp_NoMove or Swp_NoSize));
  139.   end;
  140.  
  141.   // Wait until the window size is known before creating the shape list.
  142.   ShapeList := TShapeList.Create(Width, Height);
  143.   ShapeList.AddHelp;
  144.  
  145.   DoubleBuffer.Height := Image.ClientHeight;
  146.   DoubleBuffer.Width  := Image.ClientWidth;
  147.   RedrawShapes;
  148. end;
  149.  
  150. procedure TMainForm.FormDestroy(Sender: TObject);
  151. begin
  152.   FreeAndNil(DoubleBuffer);
  153.   FreeAndNil(ShapeList);
  154.   FreeAndNil(WaveList);
  155. end;
  156.  
  157. // Intercept all keystroke events and play a WAVE file for each key press
  158. // without interpreting the key event.
  159. procedure TMainForm.AppEventsMessage(var Msg: tagMSG;
  160.   var Handled: Boolean);
  161. begin
  162.   case Msg.Message of
  163.   Wm_KeyDown,
  164.   Wm_SysKeyDown:
  165.     begin
  166.       // Handle key down events by playing a sound and drawing a shape. 
  167.       HandleKeyDown(Msg.wParam);
  168.       Handled := True;
  169.     end;
  170.   Wm_DeadChar,
  171.   Wm_Char,
  172.   Wm_KeyUp,
  173.   Wm_SysKeyUp:
  174.     // Ignore up and other key events.
  175.     Handled := True;
  176.   else
  177.     {Skip};
  178.   end;
  179. end;
  180.  
  181. // Pick a WAVE file to play based on the key that the user pressed.
  182. procedure TMainForm.HandleKeyDown(KeyCode: Word);
  183. begin
  184.   if KeyCode = Vk_Escape then
  185.     Close
  186.   else
  187.   begin
  188.     PlayWave(KeyCodeToText(KeyCode));
  189.     CreateShape;
  190.   end;
  191. end;
  192.  
  193. // Periodically transform all the shapes into the next generation
  194. // and redraw the shapes. Typically shapes grow and fade color.
  195. procedure TMainForm.TimerTimer(Sender: TObject);
  196. begin
  197.   if not IsDebuggerPresent then
  198.   begin
  199.     ForceForegroundWindow(Handle);
  200.     if Handle <> GetTopWindow(0) then
  201.       SetWindowPos(Handle, HWnd_Top, 0, 0, 0, 0, Swp_NoSize or Swp_NoMove);
  202.   end;
  203.   ShapeList.NextGeneration;
  204.   RedrawShapes;
  205. end;
  206.  
  207. // Draw all the shapes to a background bitmap, and replace
  208. // the image's bitmap with the other bitmap. This use of a
  209. // double buffer minimizes screen flicker.
  210. procedure TMainForm.RedrawShapes;
  211. begin
  212.   DoubleBuffer.Canvas.Brush.Color := clBlack;
  213.   DoubleBuffer.Canvas.FillRect(Image.BoundsRect);
  214.   ShapeList.Draw(DoubleBuffer.Canvas);
  215.   Image.Picture.Bitmap := DoubleBuffer;
  216. end;
  217.  
  218. // Play the named WAVE resource. The resource might be located in
  219. // the locale-specific DLL or in the main application. Try the DLL
  220. // first, then the application. If all else fails, use a default beep.
  221. procedure TMainForm.PlayWave(const Name: string);
  222. var
  223.   ResName: PChar;
  224. begin
  225.   ResName := StringToResID(Name);
  226.   if not PlaySound(ResName, hInstance, Snd_Resource or Snd_ASync) then
  227.     Beep;
  228. end;
  229.  
  230. // Pick a WAV resource at random and play it.
  231. procedure TMainForm.PlayRandomWave;
  232. var
  233.   ResName: PChar;
  234. begin
  235.   if WaveList.Count = 0 then
  236.     Beep
  237.   else
  238.   begin
  239.     ResName := StringToResID(WaveList[Random(WaveList.Count)]);
  240.     if not PlaySound(ResName, hInstance, Snd_Resource or Snd_ASync) then
  241.       Beep;
  242.   end;
  243. end;
  244.  
  245. // Create a new shape at (X, Y), or generate a random position.
  246. procedure TMainForm.CreateShape(X, Y: Integer);
  247. begin
  248.   if X < 0 then
  249.     X := Random(Width);
  250.   if Y < 0 then
  251.     Y := Random(Height);
  252.   ShapeList.AddShape(X, Y);
  253.   RedrawShapes;
  254. end;
  255.  
  256. // When the TImage gets a mouse down event, generate a new shape
  257. // at the mouse position, and play a random sound.
  258. procedure TMainForm.ImageMouseDown(Sender: TObject; Button: TMouseButton;
  259.   Shift: TShiftState; X, Y: Integer);
  260. begin
  261.   CreateShape(X, Y);
  262.   PlayRandomWave;
  263. end;
  264.  
  265. // If another application tries to take control, bring attention back here.
  266. procedure TMainForm.AppEventsDeactivate(Sender: TObject);
  267. begin
  268.   if not IsDebuggerPresent then
  269.     Win32Check(SetWindowPos(Handle, Hwnd_Top, 0, 0, 0, 0, Swp_NoSize or Swp_NoMove));
  270. end;
  271.  
  272. end.
  273.