home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue56 / Splat / main.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  2000-02-25  |  10.1 KB  |  331 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.     CapsLock: Boolean;
  33.     NumLock: Boolean;
  34.     ScrollLock: Boolean;
  35.     procedure CreateShape(X: Integer = -1; Y: Integer = -1);
  36.     procedure HandleKeyDown(KeyCode: Word);
  37.     procedure PlayWave(const Name: string);
  38.     procedure PlayRandomWave;
  39.     procedure RedrawShapes;
  40.     procedure WMShowWindow(var Message: TWMShowWindow); message Wm_ShowWindow;
  41.   public
  42.     { Public declarations }
  43.   end;
  44.  
  45. var
  46.   MainForm: TMainForm;
  47.  
  48. implementation
  49.  
  50. uses MMSystem, Types, KeyText, ZWave;
  51.  
  52. {$R *.DFM}
  53.  
  54. // Return True if running under control of the Delphi debugger.
  55. function IsDebuggerPresent: Boolean;
  56. begin
  57.   Result := DebugHook <> 0;
  58. end;
  59.  
  60. // Windows 98 does not let one thread steal the keyboard focus from
  61. // another thread. Ordinarily, this is a good thing, but Splat is special.
  62. // The workaround (read "hack") is to attach the current thread to the input
  63. // the foreground thread, then set the foreground window. I learned this
  64. // trick from Karl Peterson's web site:
  65. // http://www.mvps.org/vb/samples.htm
  66. function ForceForegroundWindow(Handle: HWND): Boolean;
  67. var
  68.   Foreground: HWND;
  69.   ForegroundThreadID, ThisThreadID: DWORD;
  70. begin
  71.   Foreground := GetForegroundWindow;
  72.   if Foreground = Handle then
  73.     Result := True
  74.   else
  75.   begin
  76.     ForegroundThreadID := GetWindowThreadProcessId(Foreground, nil);
  77.     ThisThreadID := GetWindowThreadProcessId(Handle, nil);
  78.  
  79.     AttachThreadInput(ThisThreadID, ForegroundThreadID, True);
  80.     SetForegroundWindow(Handle);
  81.     AttachThreadInput(ThisThreadID, ForegroundThreadID, False);
  82.  
  83.     // Return True if the trick worked.
  84.     Result := GetForegroundWindow = Handle;
  85.   end;
  86. end;
  87.  
  88. // Return a string for a resource name or identifier. A resource name
  89. // can be a string or a numeric identifier. Convert a numeric
  90. // identifier to a string as a hexadecimal constant (e.g., $12A).
  91. // The dollar sign makes it easy to convert back to a number and
  92. // to distinguish a numeric ID from a string name.
  93. function ResIDToString(ResName: PChar): string;
  94. begin
  95.   if LongRec(ResName).Hi = 0 then
  96.     Result := Format('$%x', [Integer(ResName)])
  97.   else
  98.     Result := ResName;
  99. end;
  100.  
  101. // Convert a string back to a resource identifier.
  102. function StringToResID(const ResText: string): PChar;
  103. var
  104.   ID: Word;
  105. begin
  106.   if (ResText = '') or (ResText[1] <> '$') then
  107.     Result := PChar(ResText)
  108.   else
  109.   begin
  110.     ID := StrToInt(ResText);  // Make sure the ID is within the proper bounds.
  111.     Result := PChar(ID);
  112.   end;
  113. end;
  114.  
  115. // Collect a list of all ZWAVE resources, to pick a random one to play
  116. // when the user clicks the mouse.
  117. function EnumWaves(hInstance: THandle; const ResType, ResName: PChar;
  118.     Strings: TStrings): LongBool; stdcall;
  119. begin
  120.   Strings.AddObject(ResIDToString(ResName), TObject(hInstance));
  121.   Result := True;
  122. end;
  123.  
  124. // Return True if the key with virtual key code KeyCode is
  125. // in the toggled (down) state. The caller supplies the keyboard
  126. // state so IsKeyToggled doesn't have to call GetKeyboardState repeatedly.
  127. function IsKeyToggled(const KeyState: TKeyboardState; KeyCode: Word): Boolean;
  128. begin
  129.   Result := (KeyState[KeyCode] and 1) <> 0;
  130. end;
  131.  
  132. procedure SetKeyState(KeyCode, ScanCode: Word);
  133. begin
  134.   keybd_event(KeyCode, ScanCode, KeyEventF_ExtendedKey, 0);
  135.   keybd_event(KeyCode, ScanCode, KeyEventF_ExtendedKey or KeyEventF_KeyUp, 0);
  136. end;
  137.  
  138. procedure TMainForm.FormCreate(Sender: TObject);
  139. var
  140.   KeyState: TKeyboardState;
  141. begin
  142.   // Do not randomize when debugging, so the behavior is predictable.
  143.   if not IsDebuggerPresent then
  144.     Randomize;
  145.  
  146.   // Get the keyboard state to determine the status of Caps Lock, Num Lock, and Scroll Lock.
  147.   Win32Check(GetKeyboardState(KeyState));
  148.   CapsLock   := IsKeyToggled(KeyState, Vk_Capital);
  149.   NumLock    := IsKeyToggled(KeyState, Vk_NumLock);
  150.   ScrollLock := IsKeyToggled(KeyState, Vk_Scroll);
  151.  
  152.   // Get a list of all the sound resources, so they can be played back randomly.
  153.   WaveList := TStringList.Create;
  154.   EnumResourceNames(hInstance, 'ZWAVE', @EnumWaves, LParam(WaveList));
  155.   EnumResourceNames(FindResourceHInstance(hInstance), 'ZWAVE', @EnumWaves, LParam(WaveList));
  156.  
  157.   // Create a bitmap to double-buffer the main TImage.
  158.   DoubleBuffer := TBitmap.Create;
  159.  
  160.   if not IsDebuggerPresent then
  161.   begin
  162.     // Make this window the cover the full screen
  163.     // and be topmost of all windows in all applications.
  164.     SetBounds(0, 0, Screen.Width, Screen.Height);
  165.     Win32Check(SetWindowPos(Handle, Hwnd_TopMost, 0, 0, 0, 0, Swp_NoMove or Swp_NoSize));
  166.   end;
  167.  
  168.   // Wait until the window size is known before creating the shape list.
  169.   ShapeList := TShapeList.Create(Width, Height);
  170.   ShapeList.AddHelp;
  171.  
  172.   DoubleBuffer.Height := Image.ClientHeight;
  173.   DoubleBuffer.Width  := Image.ClientWidth;
  174.   RedrawShapes;
  175. end;
  176.  
  177. procedure TMainForm.FormDestroy(Sender: TObject);
  178. const
  179.   CapsLock_ScanCode = $3A;
  180.   NumLock_ScanCode = $45;
  181.   ScrollLock_ScanCode = $46;
  182. var
  183.   KeyState: TKeyboardState;
  184. begin
  185.   // Restore the Caps Lock, Num Lock, and Scroll Lock keys.
  186.   Win32Check(GetKeyboardState(KeyState));
  187.   if IsKeyToggled(KeyState, Vk_Capital) <> CapsLock then
  188.     SetKeyState(Vk_Capital, CapsLock_ScanCode);
  189.   if IsKeyToggled(KeyState, Vk_NumLock) <> NumLock then
  190.     SetKeyState(Vk_NumLock, NumLock_ScanCode);
  191.   if IsKeyToggled(KeyState, Vk_Scroll) <> ScrollLock then
  192.     SetKeyState(Vk_Scroll,  ScrollLock_ScanCode);
  193.  
  194.   FreeAndNil(DoubleBuffer);
  195.   FreeAndNil(ShapeList);
  196.   FreeAndNil(WaveList);
  197. end;
  198.  
  199. // Intercept all keystroke events and play a WAVE file for each key press
  200. // without interpreting the key event.
  201. procedure TMainForm.AppEventsMessage(var Msg: tagMSG;
  202.   var Handled: Boolean);
  203. begin
  204.   case Msg.Message of
  205.   Wm_KeyDown,
  206.   Wm_SysKeyDown:
  207.     begin
  208.       // Handle key down events by playing a sound and drawing a shape.
  209.       HandleKeyDown(Msg.wParam);
  210.       Handled := True;
  211.     end;
  212.   Wm_DeadChar,
  213.   Wm_Char,
  214.   Wm_KeyUp,
  215.   Wm_SysKeyUp:
  216.     // Ignore up and other key events.
  217.     Handled := True;
  218.   else
  219.     {Skip};
  220.   end;
  221. end;
  222.  
  223. // Pick a WAVE file to play based on the key that the user pressed.
  224. procedure TMainForm.HandleKeyDown(KeyCode: Word);
  225. begin
  226.   if KeyCode = Vk_Escape then
  227.     Close
  228.   else
  229.   begin
  230.     PlayWave(KeyCodeToText(KeyCode));
  231.     CreateShape;
  232.   end;
  233. end;
  234.  
  235. // Periodically transform all the shapes into the next generation
  236. // and redraw the shapes. Typically shapes grow and fade color.
  237. procedure TMainForm.TimerTimer(Sender: TObject);
  238. begin
  239.   if not IsDebuggerPresent then
  240.   begin
  241.     ForceForegroundWindow(Handle);
  242.     if Handle <> GetTopWindow(0) then
  243.       SetWindowPos(Handle, HWnd_Top, 0, 0, 0, 0, Swp_NoSize or Swp_NoMove);
  244.   end;
  245.   ShapeList.NextGeneration;
  246.   RedrawShapes;
  247. end;
  248.  
  249. // Draw all the shapes to a background bitmap, and replace
  250. // the image's bitmap with the other bitmap. This use of a
  251. // double buffer minimizes screen flicker.
  252. procedure TMainForm.RedrawShapes;
  253. begin
  254.   DoubleBuffer.Canvas.Brush.Color := clBlack;
  255.   DoubleBuffer.Canvas.FillRect(Image.BoundsRect);
  256.   ShapeList.Draw(DoubleBuffer.Canvas);
  257.   Image.Picture.Bitmap := DoubleBuffer;
  258. end;
  259.  
  260. // Play the named ZWAVE resource. The resource might be located in
  261. // the locale-specific DLL or in the main application. Try the DLL
  262. // first, then the application. If all else fails, use a default beep.
  263. procedure TMainForm.PlayWave(const Name: string);
  264. var
  265.   ResName: PChar;
  266. begin
  267.   ResName := StringToResID(Name);
  268.   if not PlayCompressedSound(ResName, FindResourceHInstance(hInstance), Snd_Resource or Snd_Async or Snd_NoDefault) then
  269.     if not PlayCompressedSound(ResName, hInstance, Snd_Resource or Snd_ASync) then
  270.       Beep;
  271. end;
  272.  
  273. // Pick a WAV resource at random and play it.
  274. procedure TMainForm.PlayRandomWave;
  275. var
  276.   ResName: PChar;
  277.   ResInstance: HINST;
  278.   Wave: Integer;
  279. begin
  280.   if WaveList.Count = 0 then
  281.     Beep
  282.   else
  283.   begin
  284.     Wave := Random(WaveList.Count);
  285.     ResName := StringToResID(WaveList[Wave]);
  286.     ResInstance := HINST(WaveList.Objects[Wave]);
  287.     if not PlayCompressedSound(ResName, ResInstance, Snd_Resource or Snd_Async) then
  288.       Beep;
  289.   end;
  290. end;
  291.  
  292. // Create a new shape at (X, Y), or generate a random position.
  293. procedure TMainForm.CreateShape(X, Y: Integer);
  294. begin
  295.   if X < 0 then
  296.     X := Random(Width);
  297.   if Y < 0 then
  298.     Y := Random(Height);
  299.   ShapeList.AddShape(X, Y);
  300.   RedrawShapes;
  301. end;
  302.  
  303. // When the TImage gets a mouse down event, generate a new shape
  304. // at the mouse position, and play a random sound.
  305. procedure TMainForm.ImageMouseDown(Sender: TObject; Button: TMouseButton;
  306.   Shift: TShiftState; X, Y: Integer);
  307. begin
  308.   CreateShape(X, Y);
  309.   PlayRandomWave;
  310. end;
  311.  
  312. // If another application tries to take control, bring attention back here.
  313. procedure TMainForm.AppEventsDeactivate(Sender: TObject);
  314. begin
  315.   if not IsDebuggerPresent then
  316.     Win32Check(SetWindowPos(Handle, Hwnd_Top, 0, 0, 0, 0, Swp_NoSize or Swp_NoMove));
  317. end;
  318.  
  319. procedure TMainForm.WMShowWindow(var Message: TWMShowWindow);
  320. begin
  321.   // The user cannot minimize Splat, but pressing Windows+M
  322.   // minimizes all windows. Prevent Splat from minimizing
  323.   // by intercepting the Wm_ShowWindow message.
  324.   if not Message.Show and (Message.Status = Sw_ParentClosing) then
  325.     Message.Result := 0
  326.   else
  327.     inherited;
  328. end;
  329.  
  330. end.
  331.