I’ve been a fan of DragonRuby since its release. I’ve been programming for a while, but I’m not familiar with game programming. I’ve been creating small projects for myself to explore and learn. This time I decided to make a simple analog clock with labels for the ordinals and lines for the second, minute, and hour hand.

The entry point to a DragonRuby project is a tick method in a main.rb file. I decided to take a object-oriented this time with a Clock class. I’ve seen examples where there are .new calls the tick method, but I think it would be better to create one instance outside of tick and pass it arguments. Each tick I rendered all of the parts.

My basic structure was this:

structure I have been following a pattern of modifying state with inputs, calculating state updates, and rendering based on state; it has been working well so far. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Clock def tick ( args ) render_ordinals ( args ) render_second_hand ( args ) render_minute_hand ( args ) render_hour_hand ( args ) end def render_ordinals ( args ) end def render_second_hand ( args ) end def render_minute_hand ( args ) end def render_hour_hand ( args ) end end @clock = Clock . new def tick ( args ) @clock . tick ( args ) end

I decided to work on the second hand first because I thought it would be tricky, but, once I figured it out, the rest of the hands would be easy. I would need some constants for doing the math. I wanted to put the clock in the middle. It would have been easier if I changed the coordinate system to be in the middle (DragonRuby lets you do that), but I purposely wanted to learn the default coordinate system that goes 0-1280 in the x direction and 0-720 in the y direction. I knew that I would need to convert degrees to radians, so I put that constant in. Lastly, I wanted a radius to go off of. This was the result:

constants 1 2 3 4 5 6 7 class Clock MID_X = 1280 / 2 MID_Y = 720 / 2 RADIAN = Math :: PI / 180 RADIUS = 180 ...

For my render_second_hand method I decided I would need two parameters: the args so that I could output a line and seconds which would be the current seconds. I would need to convert the seconds to degrees around the clock’s circle ( seconds * 360/60 ), those degrees to radians ( seconds * 360/60 * RADIAN ), and trigonometric functions ( sin for y and cos for x ) to x and y coordinates. I would also need to multiply by a fraction of my radius to get the line length ( RADIUS * 0.8 ). Lastly, the trigonometric functions naturally go counterclockwise (I would need to reverse that) and start in the wrong position for my clock (I would need to add an offset). This was my result:

render_second_hand Note the negative on the radius for the `y` coordinate to make it clockwise. 1 2 3 4 5 6 def render_second_hand ( args , seconds ) seconds_x = RADIUS * 0.8 * Math . cos (( seconds + 45 ) * 360 / 60 * RADIAN ) + MID_X seconds_y = - RADIUS * 0.8 * Math . sin (( seconds + 45 ) * 360 / 60 * RADIAN ) + MID_Y args . outputs . lines << [ MID_X , MID_Y , seconds_x , seconds_y ] end

With this worked out, the render_minute_hand method was nearly identical:

render_minute_hand 1 2 3 4 5 6 def render_minute_hand ( args , minutes ) minutes_x = RADIUS * 0.6 * Math . cos (( minutes + 45 ) * 360 / 60 * RADIAN ) + MID_X minutes_y = - RADIUS * 0.6 * Math . sin (( minutes + 45 ) * 360 / 60 * RADIAN ) + MID_Y args . outputs . lines << [ MID_X , MID_Y , minutes_x , minutes_y ] end

And the render_hour_hand :

render_hour_hand Note that I divide by 12 for hours. 1 2 3 4 5 6 def render_hour_hand ( args , hours ) hours_x = RADIUS * 0.4 * Math . cos (( hours + 45 ) * 360 / 12 * RADIAN ) + MID_X hours_y = - RADIUS * 0.4 * Math . sin (( hours + 45 ) * 360 / 12 * RADIAN ) + MID_Y args . outputs . lines << [ MID_X , MID_Y , hours_x , hours_y ] end

Lastly, I needed to render the numbers (ordinals) around the outside:

render_ordinals Note that my offset changes 1 2 3 4 5 6 7 8 def render_ordinals ( args ) ( 1 .. 12 ). each do | ordinal | x = RADIUS * Math . cos (( ordinal * 30 - 90 ) * RADIAN ) + MID_X y = - RADIUS * Math . sin (( ordinal * 30 - 90 ) * RADIAN ) + MID_Y args . outputs . labels << { x: x , y: y , text: ordinal , size: 5 , alignment: 1 } end end

With all of this, my code was shaping up nicely:

main.rb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 class Clock MID_X = 1280 / 2 MID_Y = 720 / 2 RADIAN = Math :: PI / 180 RADIUS = 180 def tick ( args ) time = Time . now render_ordinals ( args ) render_second_hand ( args , time . sec ) render_minute_hand ( args , time . min ) render_hour_hand ( args , time . hour ) end def render_ordinals ( args ) ( 1 .. 12 ). each do | ordinal | x = RADIUS * Math . cos (( ordinal * 30 - 90 ) * RADIAN ) + MID_X y = - RADIUS * Math . sin (( ordinal * 30 - 90 ) * RADIAN ) + MID_Y args . outputs . labels << { x: x , y: y , text: ordinal , size: 5 , alignment: 1 } end end def render_second_hand ( args , seconds ) seconds_x = RADIUS * 0.8 * Math . cos (( seconds + 45 ) * 360 / 60 * RADIAN ) + MID_X seconds_y = - RADIUS * 0.8 * Math . sin (( seconds + 45 ) * 360 / 60 * RADIAN ) + MID_Y args . outputs . lines << [ MID_X , MID_Y , seconds_x , seconds_y ] end def render_minute_hand ( args , minutes ) minutes_x = RADIUS * 0.6 * Math . cos (( minutes + 45 ) * 360 / 60 * RADIAN ) + MID_X minutes_y = - RADIUS * 0.6 * Math . sin (( minutes + 45 ) * 360 / 60 * RADIAN ) + MID_Y args . outputs . lines << [ MID_X , MID_Y , minutes_x , minutes_y ] end def render_hour_hand ( args , hours ) hours_x = RADIUS * 0.4 * Math . cos (( hours + 45 ) * 360 / 12 * RADIAN ) + MID_X hours_y = - RADIUS * 0.4 * Math . sin (( hours + 45 ) * 360 / 12 * RADIAN ) + MID_Y args . outputs . lines << [ MID_X , MID_Y , hours_x , hours_y ] end end @clock = Clock . new def tick ( args ) @clock . tick ( args ) end

There were two last things that bothered me. First was that my ordinals didn’t line up quite right, but labels are tricky and I didn’t care to worry about each one. Secondly, the motion was jerky and abrupt. This was because I was using integers for my time values. I decided to use fractional time in my final result and it worked better:

fractional time 1 2 3 4 5 6 7 8 def tick ( args ) time = Time . now render_ordinals ( args ) render_second_hand ( args , time . sec + time . usec / 1000000 ) render_minute_hand ( args , time . min + time . sec / 60 ) render_hour_hand ( args , time . hour + time . min / 60 ) end

Thanks for reading!