Subversion Repositories gridgame

Rev

Rev 12 | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. unit GridGameMain;
  2.  
  3. // TODO: Top scores
  4. // TODO: Remember grid size and settings for next session
  5. // TODO: Settings
  6. //       - Disable Sounds
  7. //       - Disable Music
  8. // TODO: Game Modes
  9. //       - Nightmare = 1 misclick leads to reshuffle
  10. //       - Hard = 20% (?) misclicks leads reshuffle
  11. //       - Medium = Deadlock paths possible, infinite misclicks allowed
  12. //       - Easy = No deadlock paths, infinite misclicks allowed  <== current default
  13. //       When deadlocks are implemented, add a warning to the help file
  14. //       "Please be aware that there might be traps. This means paths which lead to a deadlock."
  15. // TODO: Double click should not count as misclick
  16. // TODO: Make an icon
  17. // TODO: Center the cards to the screen center
  18.  
  19. interface
  20.  
  21. uses
  22.   Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  23.   Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.Samples.Spin,
  24.   Vcl.ComCtrls, Vcl.Buttons, Vcl.ExtCtrls, Vcl.MPlayer;
  25.  
  26. const
  27.   MinGridSize = 2;
  28.   MaxGridSize = 14; // 14 Max because King has value 13
  29.  
  30. type
  31.   TCardSuit = (csUp, csDown, csLeft, csRight);
  32.  
  33.   TGameStat = record
  34.     Initialized: boolean;
  35.     GridSize: integer;
  36.     StartTime: TDateTime;
  37.     FinishTime: TDateTime;
  38.     StepsStart: integer;
  39.     StepsRemaining: integer;
  40.     AllowTraps: boolean;
  41.     MisClicksCur: integer;
  42.     MisClicksMax: integer;
  43.   end;
  44.   PGameStat = ^TGameStat;
  45.  
  46.   TCard = record
  47.     isJoker: boolean;
  48.     number: integer; //1=A, 2..10, 11=J, 12=Q, 13=K
  49.     suit: TCardSuit;
  50.     isFaceDown: boolean;
  51.     tag: integer;
  52.     btn: TBitBtn;
  53.   end;
  54.   PCard = ^TCard;
  55.  
  56.   TDeck = array[0..1{Joker}+13*4-1] of TCard;
  57.   PDeck = ^TDeck;
  58.  
  59.   TGrid = array[0..MaxGridSize-1, 0..MaxGridSize-1] of TCard;
  60.   PGrid = ^TGrid;
  61.  
  62.   TForm1 = class(TForm)
  63.     Memo1: TMemo;
  64.     Button1: TButton;
  65.     SpinEdit1: TSpinEdit;
  66.     PageControl1: TPageControl;
  67.     TabSheet1: TTabSheet;
  68.     TabSheet2: TTabSheet;
  69.     ScrollBox1: TScrollBox;
  70.     Panel1: TPanel;
  71.     Panel2: TPanel;
  72.     Panel3: TPanel;
  73.     Panel4: TPanel;
  74.     Timer1: TTimer;
  75.     Timer2: TTimer;
  76.     Label1: TLabel;
  77.     TabSheet3: TTabSheet;
  78.     Memo2: TMemo;
  79.     MediaPlayer1: TMediaPlayer;
  80.     Label2: TLabel;
  81.     procedure Button1Click(Sender: TObject);
  82.     procedure Timer1Timer(Sender: TObject);
  83.     procedure Timer2Timer(Sender: TObject);
  84.     procedure FormCreate(Sender: TObject);
  85.     procedure MediaPlayer1Notify(Sender: TObject);
  86.     procedure MediaPlayer1Click(Sender: TObject; Button: TMPBtnType;
  87.       var DoDefault: Boolean);
  88.   private
  89.     Fdeck: TDeck;
  90.     Fgrid: TGrid;
  91.     stat: TGameStat;
  92.     procedure DrawGameStat;
  93.     procedure CardClick(Sender: TObject);
  94.     procedure LaycardsToGrid_BA(AStats: PGameStat; ADeck: PDeck; AGrid: PGrid);
  95.     procedure DrawGridToScreen_BA_Print(AGrid: PGrid; AMemo: TMemo);
  96.     procedure CardButtonDraw(ACard: PCard);
  97.     procedure DrawGridToScreen_BA_Interactive(AGrid: PGrid; AParent: TWinControl);
  98.     procedure RegisterMisclick;
  99.     procedure Reshuffle;
  100.     function GetScore: integer;
  101.   end;
  102.  
  103. var
  104.   Form1: TForm1;
  105.  
  106. implementation
  107.  
  108. {$R *.dfm}
  109.  
  110. uses
  111.   Winapi.MMSystem, DateUtils, Math;
  112.  
  113. procedure InitDeck(var ADeck: TDeck);
  114. var
  115.   i: integer;
  116. begin
  117.   for i := 0 to Length(ADeck)-1 do
  118.   begin
  119.     ADeck[i].isJoker := i = 0;
  120.     ADeck[i].tag := 0;
  121.     ADeck[i].isFaceDown := false;
  122.     if i >= 1 then
  123.     begin
  124.       ADeck[i].suit := TCardSuit((i-1) mod 4);
  125.       ADeck[i].number := 1+((i-1) div 4);
  126.     end;
  127.   end;
  128. end;
  129.  
  130. procedure TForm1.LaycardsToGrid_BA(AStats: PGameStat; ADeck: PDeck; AGrid: PGrid);
  131. var
  132.   x, y: integer;
  133.   initx, inity: integer;
  134.   curx, cury: integer;
  135.   nextx, nexty: integer;
  136.   di: integer;
  137.   crit: integer;
  138.   dist: integer;
  139.   pathLength: integer;
  140. begin
  141.   // 1. Put all cards facedown
  142.   for y := 0 to stat.GridSize-1 do
  143.   begin
  144.     for x := 0 to stat.GridSize-1 do
  145.     begin
  146.       AGrid[x, y].isFaceDown := true;
  147.     end;
  148.   end;
  149.  
  150.   // 2. Search a random first card
  151.   initx := Random(stat.GridSize);
  152.   inity := Random(stat.GridSize);
  153.   AGrid[initx, inity].isJoker := true;
  154.   AGrid[initx, inity].isFaceDown := false;
  155.  
  156.   curx := initx;
  157.   cury := inity;
  158.  
  159.   nextx := 0;
  160.   nexty := 0;
  161.   dist := 0;
  162.  
  163.   // 3. Make a path through the grid
  164.   pathLength := 0;
  165.   for di := 0 to 10000 do
  166.   begin
  167.     crit := 0;
  168.     repeat
  169.       Inc(crit);
  170.       case Random(2) of
  171.         0:
  172.         begin
  173.           repeat
  174.             nextx := Random(stat.GridSize);
  175.           until nextx <> curx;
  176.           dist := Abs(nextx - curx);
  177.           nexty := cury;
  178.         end;
  179.  
  180.         1:
  181.         begin
  182.           nextx := curx;
  183.           repeat
  184.             nexty := Random(stat.GridSize);
  185.           until nexty <> cury;
  186.           dist := Abs(nexty - cury);
  187.         end;
  188.       end;
  189.     until AGrid[nextx, nexty].isFaceDown or (crit > 1000);
  190.  
  191.     if crit > 1000 then break;
  192.  
  193.     AGrid[nextx, nexty].isJoker := false;
  194.     AGrid[nextx, nexty].isFaceDown := false;
  195.     if nextx > curx then
  196.       AGrid[nextx, nexty].suit := csRight
  197.     else if nextx < curx then
  198.       AGrid[nextx, nexty].suit := csLeft
  199.     else if nexty > cury then
  200.       AGrid[nextx, nexty].suit := csDown
  201.     else
  202.       AGrid[nextx, nexty].suit := csUp;
  203.     AGrid[nextx, nexty].number := dist;
  204.     Inc(pathLength);
  205.  
  206.     curx := nextx;
  207.     cury := nexty;
  208.   end;
  209.  
  210.   // 4. Fill unused spots with junk
  211.   if stat.AllowTraps then
  212.   begin
  213.     for y := 0 to stat.GridSize-1 do
  214.     begin
  215.       for x := 0 to stat.GridSize-1 do
  216.       begin
  217.         if AGrid[x, y].isFaceDown then
  218.         begin
  219.           AGrid[x, y].isJoker := false;
  220.           AGrid[x, y].isFaceDown := false;
  221.           AGrid[x, y].suit := TCardSuit(Random(4));
  222.           AGrid[x, y].number := Random(stat.GridSize-1)+1;
  223.         end;
  224.       end;
  225.     end;
  226.   end;
  227.  
  228.   AStats.StepsStart := pathLength;
  229.   AStats.StepsRemaining := pathLength;
  230. end;
  231.  
  232. procedure TForm1.MediaPlayer1Click(Sender: TObject; Button: TMPBtnType;
  233.   var DoDefault: Boolean);
  234. begin
  235.   Case Button of
  236.     btStop:   MediaPlayer1.EnabledButtons := [btPlay];
  237.     btPlay:   MediaPlayer1.EnabledButtons := [btPause,btStop];
  238.   end;
  239. end;
  240.  
  241. procedure TForm1.MediaPlayer1Notify(Sender: TObject);
  242. var
  243.   MP: TMediaPlayer;
  244. begin
  245.   MP := TMediaPlayer(Sender);
  246.  
  247.   if MP.Position = MP.Length then
  248.   begin
  249.     if MP.Mode = mpPlaying then MP.Rewind;
  250.     MP.Play;
  251.   end;
  252.  
  253.   MP.Notify := True;
  254. end;
  255.  
  256. procedure TForm1.RegisterMisclick;
  257. begin
  258.   Inc(stat.MisClicksCur);
  259.   if (stat.MisClicksMax <> -1) and (stat.MisClicksCur > stat.MisClicksMax) then
  260.   begin
  261.     Reshuffle;
  262.   end
  263.   else
  264.   begin
  265.     DrawGameStat;
  266.     PlaySound('sounds\misclick.wav', 0, SND_FILENAME or SND_NODEFAULT or SND_ASYNC);
  267.   end;
  268. end;
  269.  
  270. function CardName(ACard: TCard): string;
  271. begin
  272.   if Acard.isFaceDown then
  273.   begin
  274.     result := '???'; // do not translate
  275.   end
  276.   else if Acard.isJoker then
  277.   begin
  278.     result := 'Jkr'; // do not translate
  279.   end
  280.   else
  281.   begin
  282.     result := '';
  283.     if ACard.suit = csUp then
  284.       result := '♣'
  285.     else if ACard.suit = csDown then
  286.       result := '♠'
  287.     else if ACard.suit = csLeft then
  288.       result := '♥'
  289.     else if ACard.suit = csRight then
  290.       result := '♦';
  291.  
  292.     if ACard.number = 1 then
  293.       result := result + ' A'
  294.     else if ACard.number = 13 then
  295.       result := result + ' K'
  296.     else if ACard.number = 12 then
  297.       result := result + ' Q'
  298.     else if ACard.number = 11 then
  299.       result := result + ' J'
  300.     else if ACard.number = 10 then
  301.       result := result + '10'
  302.     else
  303.       result := result + ' ' + IntToStr(ACard.number);
  304.   end;
  305. end;
  306.  
  307. function StrRepeat(str: string; count: integer): string;
  308. var
  309.   i: integer;
  310. begin
  311.   result := '';
  312.   for i := 0 to count-1 do
  313.     result := result + str;
  314. end;
  315.  
  316. procedure TForm1.DrawGridToScreen_BA_Print(AGrid: PGrid; AMemo: TMemo);
  317. var
  318.   x, y: integer;
  319.   card: TCard;
  320.   s: string;
  321.   linelength: integer;
  322. begin
  323.   AMemo.Clear;
  324.   linelength := 0;
  325.   for y := 0 to stat.GridSize-1 do
  326.   begin
  327.     s := '';
  328.     for x := 0 to stat.GridSize-1 do
  329.     begin
  330.       card := AGrid[x, y];
  331.       s := s + Cardname(card) + '   ';
  332.     end;
  333.     s := '♦  ' + Trim(s) + '  ♥';
  334.     AMemo.Lines.Add(s);
  335.     linelength := Length(s);
  336.     AMemo.Lines.Add('♦'+StrRepeat(' ', linelength-2)+'♥');
  337.   end;
  338.   AMemo.Lines.Insert(0, '/'+StrRepeat('♠', linelength-2)+'\');
  339.   AMemo.Lines.Insert(1, '♦'+StrRepeat(' ', linelength-2)+'♥');
  340.   AMemo.Lines.Add('\'+StrRepeat('♣', linelength-2)+'/');
  341. end;
  342.  
  343. procedure TForm1.FormCreate(Sender: TObject);
  344. begin
  345.   PageControl1.ActivePageIndex := 0;
  346. end;
  347.  
  348. procedure TForm1.CardButtonDraw(ACard: PCard);
  349. const
  350.   GuiVertSpaceReserved = 325; // incl. Taskbar etc.
  351. var
  352.   bitbtn: TBitBtn;
  353. resourcestring
  354.   S_Joker = 'Joker';
  355. begin
  356.   bitbtn := ACard.btn;
  357.  
  358.   bitbtn.TabStop := false;
  359.  
  360.   if ACard.isJoker then
  361.     bitbtn.Font.Size := 17
  362.   else
  363.     bitbtn.Font.Size := 20;
  364.  
  365.   bitbtn.Width := 60;
  366.  
  367.   // Monitor is the screen where the for is CURRENTLY at!
  368.   if ((Monitor.Height - GuiVertSpaceReserved) div stat.GridSize) < 90 then
  369.     bitbtn.Height := (Monitor.Height - GuiVertSpaceReserved) div stat.GridSize
  370.   else
  371.     bitbtn.Height := 90;
  372.  
  373.   if ACard.isJoker then
  374.     bitbtn.Font.Color := clBlue
  375.   else if (ACard.suit = csLeft) or (ACard.suit = csRight) then
  376.     bitbtn.Font.Color := clRed
  377.   else
  378.     bitbtn.Font.Color := clBlack;
  379.  
  380.   bitbtn.Caption := CardName(ACard^);
  381.   if bitbtn.Caption = '???' then bitbtn.Caption := '';       // do not translate
  382.   if bitbtn.Caption = 'Jkr' then bitbtn.Caption := S_Joker;  // do not translate
  383. end;
  384.  
  385. function TForm1.GetScore: integer;
  386. var
  387.   ms: Int64;
  388.   clc: double;
  389. begin
  390.   if (stat.StepsRemaining = 0) and (CompareValue(stat.FinishTime,0) <> 0) then
  391.     ms := MilliSecondsBetween(stat.FinishTime, stat.StartTime)
  392.   else
  393.     ms := MilliSecondsBetween(Now, stat.StartTime);
  394.  
  395.   clc := stat.StepsStart-stat.StepsRemaining; // Successful clicks
  396.   clc := clc - stat.MisClicksCur * 0.5; // Penality  0,5clicks per misclick
  397.  
  398.   if clc < 0 then clc := 0;
  399.  
  400.   if clc = 0 then exit(0);
  401.  
  402.   result := (60*1000) - round(ms/clc);
  403.   if result < 0 then result := 0;
  404. end;
  405.  
  406. procedure TForm1.DrawGameStat;
  407. var
  408.   Timer: string;
  409.   sMisClicksMax: string;
  410.   sNewCaption: string;
  411. resourcestring
  412.   S_TITLE = 'Grid Game';
  413.   S_STATS = '%d of %d steps remaining - Time: %s (%d of %s misclicks) - SCORE %d';
  414.   S_Infinite = 'infinite';
  415. begin
  416.   sNewCaption := S_TITLE;
  417.  
  418.   if not stat.Initialized then exit;
  419.  
  420.   if (stat.StepsRemaining = 0) and (CompareValue(stat.FinishTime,0) <> 0) then
  421.     Timer := TimeToStr(stat.FinishTime - stat.StartTime)
  422.   else
  423.     Timer := TimeToStr(Now - stat.StartTime);
  424.  
  425.   if stat.MisClicksMax = -1 then
  426.     sMisClicksMax := S_Infinite
  427.   else
  428.     sMisClicksMax := IntToStr(stat.MisClicksMax);
  429.  
  430.   sNewCaption := sNewCaption + Format(' - '+S_STATS, [stat.StepsRemaining, stat.StepsStart, Timer, stat.MisClicksCur, sMisClicksMax, GetScore]);
  431.  
  432.   if Caption <> sNewCaption then Caption := sNewCaption;
  433. end;
  434.  
  435. procedure TForm1.DrawGridToScreen_BA_Interactive(AGrid: PGrid; AParent: TWinControl);
  436. var
  437.   x, y: integer;
  438.   bitbtn: TBitBtn;
  439.   card: PCard;
  440.   curx, cury: integer;
  441. begin
  442.   // Clean all cards from the grid
  443.   while AParent.ControlCount > 0 do
  444.   begin
  445.     FreeAndNil(AParent.Controls[0]);
  446.   end;
  447.  
  448.   // Draw new cards
  449.   curx := 0;
  450.   cury := 0;
  451.   for y := 0 to stat.GridSize-1 do
  452.   begin
  453.     bitbtn := nil;
  454.     for x := 0 to stat.GridSize-1 do
  455.     begin
  456.       bitbtn := TBitBtn.Create(AParent);
  457.       card := @AGrid[x, y];
  458.       card^.btn := bitbtn;
  459.       CardButtonDraw(card);
  460.       bitbtn.Left := curx;
  461.       bitbtn.Top := cury;
  462.       bitbtn.Parent := AParent;
  463.       bitbtn.OnClick := CardClick;
  464.       bitbtn.Tag := y * stat.GridSize + x;
  465.       curx := curx + bitbtn.Width + 3;
  466.       Sleep(150 div stat.GridSize);
  467.       Application.ProcessMessages;
  468.     end;
  469.     curx := 0;
  470.     cury := cury + bitbtn.Height + 3;
  471.   end;
  472. end;
  473.  
  474. procedure TForm1.Reshuffle;
  475. resourcestring
  476.   S_PLEASEWAIT = 'Please wait...';
  477. var
  478.   shouldStartMusic: boolean;
  479. begin
  480.   Caption := S_PLEASEWAIT;
  481.   Timer2.Enabled := false;
  482.  
  483.   shouldStartMusic := (MediaPlayer1.Tag = 0) or (MediaPlayer1.Mode = TMPModes.mpPlaying); // <-- if the user stopped the music, don't start it again at reshuffle
  484.  
  485.   PlaySound(nil, 0, 0);
  486.   if MediaPlayer1.Mode = TMPModes.mpPlaying then
  487.   begin
  488.     MediaPlayer1.Stop;
  489.   end;
  490.   MediaPlayer1.EnabledButtons := []; // We need to do this crap because AutoEnable does not work together with the Play/Stop commands
  491.  
  492.   PlaySound('sounds\shuffle.wav', 0, SND_FILENAME or SND_NODEFAULT or SND_LOOP or SND_ASYNC);
  493.   stat.Initialized := false;
  494.   stat.GridSize := SpinEdit1.Value;
  495.   Randomize;
  496.   InitDeck(Fdeck);
  497.   LaycardsToGrid_BA(@stat, @Fdeck, @Fgrid); // Note: deck is not used at all...
  498.   DrawGridToScreen_BA_Interactive(@Fgrid, Scrollbox1);
  499.   DrawGridToScreen_BA_Print(@Fgrid, Memo1);
  500.   stat.StartTime := Now;
  501.   stat.FinishTime := 0;
  502.   stat.MisClicksCur := 0;
  503.   stat.MisClicksMax := -1; // TODO: game mode
  504.   stat.AllowTraps := false; // TODO: game mode
  505.   stat.Initialized := true;
  506.   DrawGameStat;
  507.   PlaySound(nil, 0, 0);
  508.  
  509.   MediaPlayer1.FileName := 'sounds\music.wav';
  510.   if FileExists(MediaPlayer1.FileName) then
  511.   begin
  512.     MediaPlayer1.Open;
  513.     if shouldStartMusic then
  514.     begin
  515.       MediaPlayer1.Play;
  516.       MediaPlayer1.EnabledButtons := [btStop]; // We need to do this crap because AutoEnable does not work together with the Play/Stop commands
  517.       MediaPlayer1.Notify := True;
  518.       MediaPlayer1.AutoRewind := False; // Otherwise Loop does not work
  519.       MediaPlayer1.Tag := 1; // ran once
  520.     end
  521.     else
  522.     begin
  523.       MediaPlayer1.EnabledButtons := [btPlay];
  524.     end;
  525.   end;
  526.  
  527.   Timer2.Enabled := true;
  528. end;
  529.  
  530. procedure TForm1.Button1Click(Sender: TObject);
  531. begin
  532.   Button1.Enabled := false;
  533.   try
  534.     Reshuffle;
  535.   finally
  536.     Button1.Enabled := true;
  537.   end;
  538. end;
  539.  
  540. procedure TForm1.CardClick(Sender: TObject);
  541. var
  542.   x, y: integer;
  543.   oldx, oldy: integer;
  544. begin
  545.   if not stat.Initialized then exit;
  546.   if stat.StepsRemaining = 0 then exit;
  547.  
  548.   x := TBitBtn(Sender).Tag mod stat.GridSize;
  549.   y := TBitBtn(Sender).Tag div stat.GridSize;
  550.  
  551.   if Fgrid[x, y].isJoker or Fgrid[x, y].isFaceDown then
  552.   begin
  553.     // It is theoretically a misclick, but we do not count it, because
  554.     // clicking Joker or face-down card is too invalid to be taken seriously.
  555.     // RegisterMisclick;
  556.     Exit;
  557.   end;
  558.  
  559.   if Fgrid[x, y].suit = csUp then
  560.   begin
  561.     oldx := x;
  562.     oldy := y + Fgrid[x, y].number;
  563.   end
  564.   else if Fgrid[x, y].suit = csDown then
  565.   begin
  566.     oldx := x;
  567.     oldy := y - Fgrid[x, y].number;
  568.   end
  569.   else if Fgrid[x, y].suit = csLeft then
  570.   begin
  571.     oldx := x + Fgrid[x, y].number;
  572.     oldy := y;
  573.   end
  574.   else if Fgrid[x, y].suit = csRight then
  575.   begin
  576.     oldx := x - Fgrid[x, y].number;
  577.     oldy := y;
  578.   end
  579.   else
  580.   begin
  581.     // Otherwise compiler complains
  582.     oldx := 0;
  583.     oldy := 0;
  584.   end;
  585.  
  586.   if (oldy >= 0) and (oldy < stat.GridSize) and (oldx >= 0) and (oldx < stat.GridSize) then
  587.   begin
  588.     if not Fgrid[oldx, oldy].isJoker or Fgrid[oldx, oldy].isFaceDown then
  589.     begin
  590.       RegisterMisclick;
  591.       Exit;
  592.     end
  593.     else
  594.     begin
  595.       Fgrid[oldx, oldy].isFaceDown := true;
  596.       CardButtonDraw(@Fgrid[oldx, oldy]);
  597.  
  598.       Dec(stat.StepsRemaining);
  599.  
  600.       Fgrid[x, y].isJoker := true;
  601.       CardButtonDraw(@Fgrid[x, y]);
  602.  
  603.       DrawGameStat;
  604.  
  605.       if stat.StepsRemaining = 0 then
  606.       begin
  607.         stat.FinishTime := Now;
  608.  
  609.         // Hide all cards
  610.         for y := 0 to stat.GridSize-1 do
  611.         begin
  612.           for x := 0 to stat.GridSize-1 do
  613.           begin
  614.             Fgrid[x, y].isFaceDown := true;
  615.             CardButtonDraw(@Fgrid[x, y]);
  616.           end;
  617.         end;
  618.  
  619.         PlaySound('sounds\win.wav', 0, SND_FILENAME or SND_NODEFAULT or SND_ASYNC);
  620.         if MediaPlayer1.Mode = TMPModes.mpPlaying then
  621.         begin
  622.           MediaPlayer1.Stop;
  623.           MediaPlayer1.EnabledButtons := [btPlay];
  624.           MediaPlayer1.Tag := 0; // Allow that the music starts again after reshuffling
  625.         end;
  626.       end
  627.       else
  628.       begin
  629.         PlaySound('sounds\pick.wav', 0, SND_FILENAME or SND_NODEFAULT or SND_ASYNC);
  630.       end;
  631.     end;
  632.   end
  633.   else
  634.   begin
  635.     RegisterMisclick;
  636.     Exit;
  637.   end;
  638. end;
  639.  
  640. procedure TForm1.Timer1Timer(Sender: TObject);
  641. begin
  642.   Timer1.Enabled := false;
  643.   Button1.Click;
  644. end;
  645.  
  646. procedure TForm1.Timer2Timer(Sender: TObject);
  647. begin
  648.   DrawGameStat;
  649. end;
  650.  
  651. end.
  652.