Task

Draw a clock.



More specific:

 Draw a time keeping device. It can be a stopwatch, hourglass, sundial, a mouth counting "one thousand and one", anything. Only showing the seconds is required, e.g.: a watch with just a second hand will suffice. However, it must clearly change every second, and the change must cycle every so often (one minute, 30 seconds, etc.) It must be drawn; printing a string of numbers to your terminal doesn't qualify. Both text-based and graphical drawing are OK.
 The clock is unlikely to be used to control space flights, so it needs not be hyper-accurate, but it should be usable, meaning if one can read the seconds off the clock, it must agree with the system clock.
 A clock is rarely (never?) a major application: don't be a CPU hog and poll the system timer every microsecond, use a proper timer/signal/event from your system or language instead. For a bad example, many OpenGL programs update the frame-buffer in a busy loop even if no redraw is needed, which is very undesirable for this task.
 A clock is rarely (never?) a major application: try to keep your code simple and to the point. Don't write something too elaborate or convoluted, instead do whatever is natural, concise and clear in your language.


Key points
 animate simple object
 timed event 
 polling system resources 
 code clarity


 
unit main;
 
interface
 
uses
  Winapi.Windows, System.SysUtils, System.Classes, Vcl.Graphics, Vcl.Forms,
  Vcl.ExtCtrls;
 
type
  TClock = class(TForm)
    tmrTimer: TTimer;
    procedure FormResize(Sender: TObject);
    procedure tmrTimerTimer(Sender: TObject);
  private
    { Private declarations }
    const
      degrees06 = PI / 30;
      degrees30 = degrees06 * 5;
      degrees90 = degrees30 * 3;
      margin = 20;
    var
      p0: TPoint;
      MinP0XY: Integer;
    class function IfThen(Condition: Boolean; TrueValue, FalseValue: Integer):
      Integer; overload; static;
    class function IfThen(Condition: Boolean; TrueValue, FalseValue: Double):
      Double; overload; static;
    procedure Paint; override;
    procedure DrawHand(Color: TColor; Angle, Size: Double; aWidth: Integer = 2);
    procedure DrawFace;
    procedure DrawCenter;
    procedure DrawNumbers(Angle: Double; Value: Integer);
  public
    { Public declarations }
  end;
 
var
  Clock: TClock;
 
implementation
 
{$R *.dfm}
 
{ TClock }
 
procedure TClock.DrawCenter;
var
  radius: Integer;
begin
  radius := 6;
  with Canvas do
  begin
    pen.Color := clNone;
    Brush.Color := clBlack;
    Ellipse(p0.x - radius, p0.y - radius, p0.x + radius, p0.y + radius);
  end;
end;
 
procedure TClock.DrawFace;
var
  radius, h, m: Integer;
begin
  radius := MinP0XY - margin;
  with Canvas do
  begin
    Pen.Color := clBlack;
    Pen.Width := 2;
    Brush.Color := clWhite;
    Ellipse(p0.x - radius, p0.y - radius, p0.x + radius, p0.y + radius);
    for m := 0 to 59 do
      DrawHand(clGray, m * degrees06, -0.08, 2);
 
    for h := 0 to 11 do
    begin
      DrawHand(clBlack, h * degrees30, -0.09, 3);
      DrawNumbers((h + 3) * degrees30, 12 - h);
    end;
  end;
end;
 
procedure TClock.DrawHand(Color: TColor; Angle, Size: Double; aWidth: Integer = 2);
var
  radius, x0, y0, x1, y1: Integer;
begin
  radius := MinP0XY - margin;
 
  x0 := p0.X + (IfThen(Size > 0, 0, Round(radius * (Size + 1) * cos(Angle))));
  y0 := p0.Y + (IfThen(Size > 0, 0, Round(radius * (Size + 1) * sin(-Angle))));
 
  x1 := p0.X + round(radius * IfThen(Size > 0, Size, 1) * cos(Angle));
  y1 := p0.y + round(radius * IfThen(Size > 0, Size, 1) * sin(-Angle));
 
  with Canvas do
  begin
    Pen.Color := Color;
    pen.Width := aWidth;
    MoveTo(x0, y0);
    LineTo(x1, y1);
  end;
end;
 
procedure TClock.DrawNumbers(Angle: Double; Value: Integer);
var
  radius, x0, y0, x1, y1, h, w: Integer;
  Size: Double;
  s: string;
begin
  radius := MinP0XY - margin;
  Size := 0.85;
  s := (Value).ToString;
 
  x1 := p0.X + round(radius * Size * cos(Angle));
  y1 := p0.y + round(radius * Size * sin(-Angle));
 
  with Canvas do
  begin
    radius := 5;
    Font.Size := 12;
    w := TextWidth(s);
    h := TextHeight(s);
 
    x0 := x1 - (w div 2);
    y0 := y1 - (h div 2);
 
    TextOut(x0, y0, s);
  end;
end;
 
procedure TClock.FormResize(Sender: TObject);
begin
  p0 := Tpoint.create(ClientRect.CenterPoint);
  MinP0XY := p0.x;
  if MinP0XY > p0.Y then
    MinP0XY := p0.y;
  Refresh();
end;
 
class function TClock.IfThen(Condition: Boolean; TrueValue, FalseValue: Double): Double;
begin
  if Condition then
    exit(TrueValue);
  exit(FalseValue);
end;
 
class function TClock.IfThen(Condition: Boolean; TrueValue, FalseValue: Integer): Integer;
begin
  if Condition then
    exit(TrueValue);
  exit(FalseValue);
end;
 
procedure TClock.Paint;
var
  t: TDateTime;
  second, minute, hour: Integer;
  angle, minsecs, hourmins: Double;
begin
  inherited;
 
  t := time;
  second := trunc(Frac(t * 24 * 60) * 60);
  minute := trunc(Frac(t * 24) * 60);
  hour := trunc(t * 24);
 
  DrawFace;
 
  angle := degrees90 - (degrees06 * second);
  DrawHand(clred, angle, 0.95, 3);
 
  minsecs := (minute + second / 60.0);
 
  angle := degrees90 - (degrees06 * minsecs);
  DrawHand(clGreen, angle, 0.8, 4);
 
  hourmins := (hour + minsecs / 60.0);
  angle := degrees90 - (degrees30 * hourmins);
  DrawHand(clBlue, angle, 0.6, 5);
 
  DrawCenter;
 
  Caption := Format('%.2d:%.2d:%.2d', [hour, minute, second]);
end;
 
procedure TClock.tmrTimerTimer(Sender: TObject);
begin
  Refresh;
end;
 
end.
 
 