Draw a clock: Difference between revisions

From Rosetta Code
Content added Content deleted
(add Ruby)
(→‎{{header|Ruby}}: add Berlin-Uhr clock)
Line 466: Line 466:


animate(5) {update}
animate(5) {update}
end</lang>

Inspired by the PicoLisp solution, here's an implementation of the Berlin-Uhr clock.
[[File:berlin_uhr.rb.png|thumb|Berlin-Uhr clock]]
<lang ruby>Shoes.app(:title => "Berlin-Uhr Clock", :width => 209, :height => 300) do
background lightgrey

Red = rgb(255, 20, 20)
Yellow = rgb(173, 255, 47)
Green = rgb(154, 205, 50)
Gray = rgb(128, 128, 128)

@time = para(:align => "center")
stack do
fill Gray
stroke black
strokewidth 2
@seconds = oval 75, 3, 50
@hrs_a = 4.times.collect {|i| rect 51*i, 56, 48, 30, 4}
@hrs_b = 4.times.collect {|i| rect 51*i, 89, 48, 30, 4}
@mins_a = 11.times.collect {|i| rect 2+18*i, 122, 15, 30, 4}
@mins_b = 4.times.collect {|i| rect 51*i, 155, 48, 30, 4}
# some decoration
fill white
stroke darkslategray
rect -10, -30, 75, 70, 10
rect 140, -30, 75, 70, 10
rect -13, 192, 105, 100, 10
rect 110, 192, 105, 100, 10
end.move(3,20)
animate(1) do
now = Time.now
@time.text = now.strftime("%H:%M:%S")
@seconds.style(:fill => now.sec.even? ? Green : Gray)
a, b = now.hour.divmod(5)
4.times {|i| @hrs_a[i].style(:fill => i < a ? Red : Gray)}
4.times {|i| @hrs_b[i].style(:fill => i < b ? Red : Gray)}
a, b = now.min.divmod(5)
11.times {|i| @mins_a[i].style(:fill => i < a ? (i%3==2 ? Red : Yellow) : Gray)}
4.times {|i| @mins_b[i].style(:fill => i < b ? Yellow : Gray)}
end
keypress do |key|
case key
when :control_q, "\x11" then exit
end
end
end</lang>
end</lang>



Revision as of 16:19, 22 November 2011

Draw a clock is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

Task: draw a clock. More specific:

  1. 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.
  2. 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.
  3. 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 framebuffer in a busy loop even if no redraw is needed, which is very undesirable for this task.
  4. 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.

AutoHotkey

requires the GDI+ Library from http://www.autohotkey.com/forum/viewtopic.php?t=32238 this code from http://www.autohotkey.com/forum/viewtopic.php?p=231836#231836 draws a very nice clock with GDI+ <lang AHK>; gdi+ ahk analogue clock example written by derRaphael

Parts based on examples from Tic's GDI+ Tutorials and of course on his GDIP.ahk
This code has been licensed under the terms of EUPL 1.0
  1. SingleInstance, Force
  2. NoEnv

SetBatchLines, -1

Uncomment if Gdip.ahk is not in your standard library
  1. Include, Gdip.ahk

If !pToken := Gdip_Startup() {

  MsgBox, 48, gdiplus error!, Gdiplus failed to start. Please ensure you have gdiplus on your system
  ExitApp

} OnExit, Exit

SysGet, MonitorPrimary, MonitorPrimary SysGet, WA, MonitorWorkArea, %MonitorPrimary% WAWidth := WARight-WALeft WAHeight := WABottom-WATop

Gui, 1: -Caption +E0x80000 +LastFound +AlwaysOnTop +ToolWindow +OwnDialogs Gui, 1: Show, NA hwnd1 := WinExist()

ClockDiameter := 180 Width := Height := ClockDiameter + 2  ; make width and height slightly bigger to avoid cut away edges CenterX := CenterY := floor(ClockDiameter/2) ; Center x

Prepare our pGraphic so we have a 'canvas' to work upon
  hbm := CreateDIBSection(Width, Height), hdc := CreateCompatibleDC()
  obm := SelectObject(hdc, hbm), G := Gdip_GraphicsFromHDC(hdc)
  Gdip_SetSmoothingMode(G, 4)
Draw outer circle
  Diameter := ClockDiameter
  pBrush := Gdip_BrushCreateSolid(0x66008000)
  Gdip_FillEllipse(G, pBrush, CenterX-(Diameter//2), CenterY-(Diameter//2),Diameter, Diameter)
  Gdip_DeleteBrush(pBrush)
Draw inner circle
  Diameter := ceil(ClockDiameter - ClockDiameter*0.08)  ; inner circle is 8 % smaller than clock's diameter
  pBrush := Gdip_BrushCreateSolid(0x80008000)
  Gdip_FillEllipse(G, pBrush, CenterX-(Diameter//2), CenterY-(Diameter//2),Diameter, Diameter)
  Gdip_DeleteBrush(pBrush)
Draw Second Marks
  R1 := Diameter//2-1                        ; outer position
  R2 := Diameter//2-1-ceil(Diameter//2*0.05) ; inner position
  Items := 60                                ; we have 60 seconds
  pPen := Gdip_CreatePen(0xff00a000, floor((ClockDiameter/100)*1.2)) ; 1.2 % of total diameter is our pen width
  GoSub, DrawClockMarks
  Gdip_DeletePen(pPen)
Draw Hour Marks
  R1 := Diameter//2-1                       ; outer position
  R2 := Diameter//2-1-ceil(Diameter//2*0.1) ; inner position
  Items := 12                               ; we have 12 hours
  pPen := Gdip_CreatePen(0xc0008000, ceil((ClockDiameter//100)*2.3)) ; 2.3 % of total diameter is our pen width
  GoSub, DrawClockMarks
  Gdip_DeletePen(pPen)
  
  ; The OnMessage will let us drag the clock
  OnMessage(0x201, "WM_LBUTTONDOWN")
  UpdateLayeredWindow(hwnd1, hdc, WALeft+((WAWidth-Width)//2), WATop+((WAHeight-Height)//2), Width, Height)
  SetTimer, sec, 1000

sec:

prepare to empty previously drawn stuff
  Gdip_SetSmoothingMode(G, 1)   ; turn off aliasing
  Gdip_SetCompositingMode(G, 1) ; set to overdraw
  
delete previous graphic and redraw background
  Diameter := ceil(ClockDiameter - ClockDiameter*0.18)  ; 18 % less than clock's outer diameter
  
  ; delete whatever has been drawn here
  pBrush := Gdip_BrushCreateSolid(0x00000000) ; fully transparent brush 'eraser'
  Gdip_FillEllipse(G, pBrush, CenterX-(Diameter//2), CenterY-(Diameter//2),Diameter, Diameter)
  Gdip_DeleteBrush(pBrush)
  
  Gdip_SetCompositingMode(G, 0) ; switch off overdraw
  pBrush := Gdip_BrushCreateSolid(0x66008000)
  Gdip_FillEllipse(G, pBrush, CenterX-(Diameter//2), CenterY-(Diameter//2),Diameter, Diameter)
  Gdip_DeleteBrush(pBrush)
  pBrush := Gdip_BrushCreateSolid(0x80008000)
  Gdip_FillEllipse(G, pBrush, CenterX-(Diameter//2), CenterY-(Diameter//2),Diameter, Diameter)
  Gdip_DeleteBrush(pBrush)
  
Draw HoursPointer
  Gdip_SetSmoothingMode(G, 4)   ; turn on antialiasing
  t := A_Hour*360//12 + (A_Min*360//60)//12 +90 
  R1 := ClockDiameter//2-ceil((ClockDiameter//2)*0.5) ; outer position
  pPen := Gdip_CreatePen(0xa0008000, floor((ClockDiameter/100)*3.5))
  Gdip_DrawLine(G, pPen, CenterX, CenterY
     , ceil(CenterX - (R1 * Cos(t * Atan(1) * 4 / 180)))
     , ceil(CenterY - (R1 * Sin(t * Atan(1) * 4 / 180))))
  Gdip_DeletePen(pPen)
  
Draw MinutesPointer
  t := A_Min*360//60+90 
  R1 := ClockDiameter//2-ceil((ClockDiameter//2)*0.25) ; outer position
  pPen := Gdip_CreatePen(0xa0008000, floor((ClockDiameter/100)*2.7))
  Gdip_DrawLine(G, pPen, CenterX, CenterY
     , ceil(CenterX - (R1 * Cos(t * Atan(1) * 4 / 180)))
     , ceil(CenterY - (R1 * Sin(t * Atan(1) * 4 / 180))))
  Gdip_DeletePen(pPen)
Draw SecondsPointer
  t := A_Sec*360//60+90 
  R1 := ClockDiameter//2-ceil((ClockDiameter//2)*0.2) ; outer position
  pPen := Gdip_CreatePen(0xa000FF00, floor((ClockDiameter/100)*1.2))
  Gdip_DrawLine(G, pPen, CenterX, CenterY
     , ceil(CenterX - (R1 * Cos(t * Atan(1) * 4 / 180)))
     , ceil(CenterY - (R1 * Sin(t * Atan(1) * 4 / 180))))
  Gdip_DeletePen(pPen)
  
  UpdateLayeredWindow(hwnd1, hdc) ;, xPos, yPos, ClockDiameter, ClockDiameter)

return

DrawClockMarks:

  Loop, % Items
     Gdip_DrawLine(G, pPen
        , CenterX - ceil(R1 * Cos(((a_index-1)*360//Items) * Atan(1) * 4 / 180))
        , CenterY - ceil(R1 * Sin(((a_index-1)*360//Items) * Atan(1) * 4 / 180))
        , CenterX - ceil(R2 * Cos(((a_index-1)*360//Items) * Atan(1) * 4 / 180))
        , CenterY - ceil(R2 * Sin(((a_index-1)*360//Items) * Atan(1) * 4 / 180)) )

return

WM_LBUTTONDOWN() {

  PostMessage, 0xA1, 2
  return

}

esc:: Exit:

  SelectObject(hdc, obm)
  DeleteObject(hbm)
  DeleteDC(hdc)
  Gdip_DeleteGraphics(G)
  Gdip_Shutdown(pToken)
  ExitApp

Return</lang>


C

Draws a crude clock in terminal. C99, compiled with gcc -std=c99. <lang C>#include <stdio.h>

  1. include <stdlib.h>
  2. include <math.h>
  3. include <time.h>
  4. include <sys/time.h>
  1. define PI 3.14159265

char * shades = " .:-*ca&#%@";

/* distance of (x, y) from line segment (0, 0)->(x0, y0) */ double dist(double x, double y, double x0, double y0) { double l = (x * x0 + y * y0) / (x0 * x0 + y0 * y0);

if (l > 1) { x -= x0; y -= y0; } else if (l >= 0) { x -= l * x0; y -= l * y0; } return sqrt(x * x + y * y); }

enum { sec = 0, min, hur }; // for subscripts

void draw(int size) {

  1. define for_i for(int i = 0; i < size; i++)
  2. define for_j for(int j = 0; j < size * 2; j++)

double angle, cx = size / 2.; double sx[3], sy[3], sw[3]; double fade[] = { 1, .35, .35 }; /* opacity of each arm */ struct timeval tv; struct tm *t;

/* set width of each arm */ sw[sec] = size * .02; sw[min] = size * .03; sw[hur] = size * .05;

every_second: gettimeofday(&tv, 0); t = localtime(&tv.tv_sec);

angle = t->tm_sec * PI / 30; sy[sec] = -cx * cos(angle); sx[sec] = cx * sin(angle);

angle = (t->tm_min + t->tm_sec / 60.) / 30 * PI; sy[min] = -cx * cos(angle) * .8; sx[min] = cx * sin(angle) * .8;

angle = (t->tm_hour + t->tm_min / 60.) / 6 * PI; sy[hur] = -cx * cos(angle) * .6; sx[hur] = cx * sin(angle) * .6;

printf("\033[s"); /* save cursor position */ for_i { printf("\033[%d;0H", i); /* goto row i, col 0 */ double y = i - cx; for_j { double x = (j - 2 * cx) / 2;

int pix = 0; /* calcs how far the "pixel" is from each arm and set * shade, with some anti-aliasing. It's ghetto, but much * easier than a real scanline conversion. */ for (int k = hur; k >= sec; k--) { double d = dist(x, y, sx[k], sy[k]); if (d < sw[k] - .5) pix = 10 * fade[k]; else if (d < sw[k] + .5) pix = (5 + (sw[k] - d) * 10) * fade[k]; } putchar(shades[pix]); } } printf("\033[u"); /* restore cursor pos so you can bg the job -- value unclear */

fflush(stdout); sleep(1); /* sleep 1 can at times miss a second, but will catch up next update */ goto every_second; }

int main(int argc, char *argv[]) { int s; if (argc <= 1 || (s = atoi(argv[1])) <= 0) s = 20; draw(s); }</lang>

GUISS

<lang guiss>Start,Programs,Accessories,Analogue Clock</lang>

JavaScript

Tested on Gecko. Put the following in a <script> tag somewhere, and call init_clock() after body load. <lang JavaScript>var sec_old = 0; function update_clock() { var t = new Date(); var arms = [t.getHours(), t.getMinutes(), t.getSeconds()]; if (arms[2] == sec_old) return; sec_old = arms[2];

var c = document.getElementById('clock'); var ctx = c.getContext('2d'); ctx.fillStyle = "rgb(0,200,200)"; ctx.fillRect(0, 0, c.width, c.height); ctx.fillStyle = "white"; ctx.fillRect(3, 3, c.width - 6, c.height - 6); ctx.lineCap = 'round';

var orig = { x: c.width / 2, y: c.height / 2 }; arms[1] += arms[2] / 60; arms[0] += arms[1] / 60; draw_arm(ctx, orig, arms[0] * 30, c.width/2.5 - 15, c.width / 20, "green"); draw_arm(ctx, orig, arms[1] * 6, c.width/2.2 - 10, c.width / 30, "navy"); draw_arm(ctx, orig, arms[2] * 6, c.width/2.0 - 6, c.width / 100, "maroon"); }

function draw_arm(ctx, orig, deg, len, w, style) { ctx.save(); ctx.lineWidth = w; ctx.lineCap = 'round'; ctx.translate(orig.x, orig.y); ctx.rotate((deg - 90) * Math.PI / 180); ctx.strokeStyle = style; ctx.beginPath(); ctx.moveTo(-len / 10, 0); ctx.lineTo(len, 0); ctx.stroke(); ctx.restore(); }

function init_clock() { var clock = document.createElement('canvas'); clock.width = 100; clock.height = 100; clock.id = "clock"; document.body.appendChild(clock);

window.setInterval(update_clock, 200); }</lang>

Liberty BASIC

LB has a timer to call a routine at regular intervals. The example is a cut-down version of the full clock supplied with LB as an example. <lang lb>

   WindowWidth  =120
   WindowHeight =144
   nomainwin
   open "Clock" for graphics_nsb_nf as #clock
   #clock "trapclose [exit]"
   #clock "fill white"
   for angle =0 to 330 step 30
       #clock "up ; home ; north ; turn "; angle
       #clock "go 40 ; down ; go 5"
   next angle
   #clock "flush"
   timer 1000, [display]
   wait

[display] ' called only when seconds have changed

   time$   =time$()
   seconds =val( right$( time$, 2))
   ' delete the last drawn segment, if there is one
   if segId >2 then #clock "delsegment "; segId -1
   ' center the turtle
   #clock "up ; home ; down ; north"
   ' erase each hand if its position has changed
   if oldSeconds <>seconds then #clock, "size 1 ; color white ; turn "; oldSeconds *6 ; " ; go 38 ; home ; color black ; north" : oldSeconds =seconds
   ' redraw all three hands, second hand first
   #clock "size 1 ; turn "; seconds * 6 ; " ; go 38"
   ' flush to end segment, then get the next segment id #
   #clock "flush"
   #clock "segment"
   input #clock, segId
   wait

[exit]

   close #clock
   end

</lang>

PicoLisp

This is an animated ASCII drawing of the "Berlin-Uhr", a clock built to display the time according to the principles of set theory, which is installed in Berlin since 1975. See www.surveyor.in-berlin.de/berlin/uhr/indexe.html and www.cs.utah.edu/~hatch/berlin_uhr.html. <lang PicoLisp>(de draw Lst

  (for L Lst
     (for X L
        (cond
           ((num? X) (space X))
           ((sym? X) (prin X))
           (T (do (car X) (prin (cdr X)))) ) )
     (prinl) ) )

(de bigBox (N)

  (do 2
     (prin "|")
     (for I 4
        (prin (if (> I N) "          |" " ======== |")) )
     (prinl) ) )

(call 'clear) # Clear screen (call "tput" "civis") # Set cursor invisible

(push '*Bye '(call "tput" "cnorm")) # Set cursor visible on exit

(loop

  (call "tput" "cup" 0 0)  # Cursor to top left
  (let Time (time (time))
     (draw (20 (5 . _)) (19 / 5 \\))
     (if (onOff (NIL))
        (draw (18 / 7 \\) (18 \\ 7 /))
        (draw (18 / 2 (3 . "#") 2 \\) (18 \\ 2 (3 . "#") 2 /)) )
     (draw
        (19 \\ (5 . _) /)
        (+ (10 . -) + (10 . -) + (10 . -) + (10 . -) +) )
     (bigBox (/ (car Time) 5))
     (draw (+ (10 . -) + (10 . -) + (10 . -) + (10 . -) +))
     (bigBox (% (car Time) 5))
     (draw (+ (43 . -) +))
     (do 2
        (prin "|")
        (for I `(range 5 55 5)
           (prin
              (cond
                 ((> I (cadr Time)) "   |")
                 ((=0 (% I 3)) " # |")
                 (T " = |") ) ) )
        (prinl) )
     (draw (+ (43 . -) +))
     (bigBox (% (cadr Time) 5))
     (draw (+ (10 . -) + (10 . -) + (10 . -) + (10 . -) +)) )
  (wait 1000) )</lang>

The six '#' characters in the "circle" on top toggle on/off every second. This is the display at 17:46:

                    _____
                   /     \
                  /  ###  \
                  \  ###  /
                   \_____/
+----------+----------+----------+----------+
| ======== | ======== | ======== |          |
| ======== | ======== | ======== |          |
+----------+----------+----------+----------+
| ======== | ======== |          |          |
| ======== | ======== |          |          |
+-------------------------------------------+
| = | = | # | = | = | # | = | = | # |   |   |
| = | = | # | = | = | # | = | = | # |   |   |
+-------------------------------------------+
| ======== |          |          |          |
| ======== |          |          |          |
+----------+----------+----------+----------+

Ruby

Library: Shoes
Sample display of Ruby solution

<lang ruby>Shoes.app(:width=>205, :height => 228, :title => "A Clock") do

 def draw_ray(width, start, stop, ratio)
   angle = Math::PI * 2 * ratio - Math::PI/2
   strokewidth width
   cos = Math::cos(angle)
   sin = Math::sin(angle)
   line 101+cos*start, 101+sin*start, 101+cos*stop, 101+sin*stop
 end
 def update
   t = Time.now
   @time.text = t.strftime("%H:%M:%S")
   h, m, s = (t.hour % 12).to_f, t.min.to_f, t.sec.to_f
   s += t.to_f - t.to_i  # add the fractional seconds
   @hands.clear do
     draw_ray(3, 0, 70, (h + m/60)/12)
     draw_ray(2, 0, 90, (m + s/60)/60)
     draw_ray(1, 0, 95, s/60)
   end
 end
 # a place for the text display
 @time = para(:align=>"center", :family => "monospace")
 # draw the clock face
 stack(:width=>203, :height=>203) do
   strokewidth 1
   fill gradient(deepskyblue, aqua)
   oval 1, 1, 200
   fill black
   oval 98, 98, 6
   # draw the minute indicators
   0.upto(59) {|m| draw_ray(1, (m % 5 == 0 ? 96 : 98), 100, m.to_f/60)}
 end.move(0,23)
 # the drawing area for the hands
 @hands = stack(:width=>203, :height=>203) {}.move(0,23)
 animate(5) {update}

end</lang>

Inspired by the PicoLisp solution, here's an implementation of the Berlin-Uhr clock.

Berlin-Uhr clock

<lang ruby>Shoes.app(:title => "Berlin-Uhr Clock", :width => 209, :height => 300) do

 background lightgrey
 Red = rgb(255, 20, 20)
 Yellow = rgb(173, 255, 47)
 Green = rgb(154, 205, 50)
 Gray = rgb(128, 128, 128)
 @time = para(:align => "center")
 stack do
   fill Gray
   stroke black
   strokewidth 2
   @seconds = oval 75, 3, 50
   @hrs_a  =  4.times.collect {|i| rect   51*i,  56, 48, 30, 4}
   @hrs_b  =  4.times.collect {|i| rect   51*i,  89, 48, 30, 4}
   @mins_a = 11.times.collect {|i| rect 2+18*i, 122, 15, 30, 4}
   @mins_b =  4.times.collect {|i| rect   51*i, 155, 48, 30, 4}
   # some decoration
   fill white
   stroke darkslategray
   rect -10, -30, 75, 70, 10
   rect 140, -30, 75, 70, 10
   rect -13, 192, 105, 100, 10
   rect 110, 192, 105, 100, 10
 end.move(3,20)
   
 animate(1) do
   now = Time.now
   @time.text = now.strftime("%H:%M:%S")
   @seconds.style(:fill => now.sec.even? ? Green : Gray)
   a, b = now.hour.divmod(5)
   4.times {|i| @hrs_a[i].style(:fill => i < a ? Red : Gray)}
   4.times {|i| @hrs_b[i].style(:fill => i < b ? Red : Gray)}
   a, b = now.min.divmod(5)
   11.times {|i| @mins_a[i].style(:fill => i < a ? (i%3==2 ? Red : Yellow) : Gray)}
   4.times  {|i| @mins_b[i].style(:fill => i < b ? Yellow : Gray)}
 end
 
 keypress do |key|
   case key
   when :control_q, "\x11" then exit
   end
 end

end</lang>

Tcl

Sample display of Tcl solution
Library: Tk

<lang tcl>package require Tcl 8.5 package require Tk

  1. GUI code

pack [canvas .c -width 200 -height 200] .c create oval 20 20 180 180 -width 10 -fill {} -outline grey70 .c create line 0 0 1 1 -tags hour -width 6 -cap round -fill black .c create line 0 0 1 1 -tags minute -width 4 -cap round -fill black .c create line 0 0 1 1 -tags second -width 2 -cap round -fill grey30 proc updateClock t {

   scan [clock format $t -format "%H %M %S"] "%d%d%d" h m s
   # On an analog clock, the hour and minute hands move gradually
   set m [expr {$m + $s/60.0}]
   set h [expr {($h % 12 + $m/60.0) * 5}]
   foreach tag {hour minute second} value [list $h $m $s] len {50 80 80} {

.c coords $tag 100 100 \ [expr {100 + $len*sin($value/30.0*3.14159)}] \ [expr {100 - $len*cos($value/30.0*3.14159)}]

   }

}

  1. Timer code, accurate to within a quarter second

set time 0 proc ticker {} {

   global time
   set t [clock seconds]
   after 250 ticker
   if {$t != $time} {

set time $t updateClock $t

   }

} ticker</lang> Note that though this code does poll the system timer approximately four times a second, this is a cheap operation; the GUI update (the relatively expensive part) only happens once a second. The amount of system processing power consumed by this code isn't noticeable on my system; it vanishes with respect to the other processing normally happening.