-- Analogue Clock
-- (c) Ramdor 2013
-- v1.6
--
-- changes
-- v1.6 - added non filled face option (bFaceFilled = true  ... is the default)
-- v1.5 - now uses computercraft paintutils line, re-worked circle code, auto find monitor
-- v1.4 - colours added and some loop derps fixed
-- v1.3 - added resize/auto scale and 0.5 textsize
--        to double the effective resolution
-- v1.2 - changed timer code
-- v1.1 - tidy up
-- v1.0 - initial release
--
-- You may edit/copy/redistribute/whatever this
-- code in any way.  Just a credit listing this
-- pastebin link (pastebin.com/LGNUpDzz) will be fine :)
-- Have fun, I did :)
 
-- see  http://www.youtube.com/watch?v=RrgOIGW0C-s   for some more info
 
-- these two settings will hide/show
-- the M and H that is on the end of the hand
local bShowHourChar = false
local bShowMinuteChar = false
local bFaceFilled = true
 
-- coloured clocks anyone?
local nBackColour = colors.brown
local nFaceColour = colors.lightGray
local nHandColour = colors.white
local nTickColour = colors.white
 
-- find the monitor and set it up
local monitor = nil
local i,side
for i, side in pairs(rs.getSides()) do
  if peripheral.getType(side) == "monitor" then
    monitor = peripheral.wrap(side)
    break
  end
end
if monitor==nil then
  print("NO MONITOR FOUND")
  print("Please attach a monitor and try again.")
  return
end
 
-- calculate scaling
monitor.setTextScale(0.5)

-- hackish fix to enable large monitor arrays enough time to initialise?
os.sleep(1)

local mw,mh = monitor.getSize()
mw = mw / (15/10) -- 15x10 is w,h of 0.5 scale single monitor
local minSize = (math.min(mw,mh) / 2) - 2
 
-- clock radius in character. 18 is good for a 6x6 advanced monitor
-- or you can use minSize which attempts to fit
-- to monitor size automatically
local nClockRad = minSize
 
-- set up the screens
term.clear()
term.setCursorPos(1,1)
print("Clock by Ramdor (c) 2013")
print("Press 'Q' to terminate.")
term.redirect(monitor)
term.setBackgroundColor(nBackColour)
term.clear()
 
-- some variables
local h = 0 -- parsed hour
local m = 0 -- parsed minute
local nOldHX = -1
local nOldHY = -1
local nOldMX = -1
local nOldMY = -1
 
local function plot(s,x,y)
  term.setCursorPos(x,y)
  term.write(s)
end
 
-- the clock face ticks
local function ticks(nW,nH)
  local nIndex
  local x
  local y
  local xi
  local yi
  local nInRadius = nClockRad-(nClockRad/4)
  local nOutRadius = nClockRad-2
 
  local nxOutRad = nOutRadius + (nOutRadius/2)
  local nyOutRad = nOutRadius
  local nxInRad = nInRadius + (nInRadius/2)
  local nyInRad = nInRadius
 
  for nIndex=0,359,(360/12) do
    x = nW + math.sin(math.rad(nIndex)) * nxOutRad
    y = nH + math.cos(math.rad(nIndex)) * nyOutRad
 
    xi = nW + math.sin(math.rad(nIndex)) * nxInRad
    yi = nH + math.cos(math.rad(nIndex)) * nyInRad
   
    paintutils.drawLine(xi,yi,x,y,nTickColour)
  end
end
 
-- draw the face (circle)
-- by working out a quarter of the points
-- then drawing lines from oposite quarter
-- results in much faster circle code
local function face(nW,nH,nRadius)
  local nIndex
  local x
  local y
  local nxRad = nRadius + (nRadius/2)
  local nyRad = nRadius
  local nOldX = -1
  local nOldY = -1
 
  local points = {}
  local xy
  local n = 1
 
  for nIndex=0,89 do
    x = math.sin(math.rad(nIndex)) * nxRad
    y = math.cos(math.rad(nIndex)) * nyRad
    x = math.floor(x)
    y = math.floor(y)
 
    -- only store positions if they are different from last time  
    if x~=nOldX or y~=nOldY then
      xy = {}
      xy[1] = x
      xy[2] = y
      points[n] = xy
     
      if y~=nOldY or not bFaceFilled then
        n=n+1
      end
 
      nOldX = x
      nOldY = y
    end
  end
 
  -- plot the circle
  for nIndex=1,#points do
    x = points[nIndex][1]
    y = points[nIndex][2]
 
    if bFaceFilled then
      paintutils.drawLine(nW-x,nH+y,nW+x,nH+y,nFaceColour)
      paintutils.drawLine(nW-x,nH-y,nW+x,nH-y,nFaceColour)
    else
      paintutils.drawPixel(nW-x,nH+y,nFaceColour)
      paintutils.drawPixel(nW+x,nH+y,nFaceColour)
      paintutils.drawPixel(nW-x,nH-y,nFaceColour)
      paintutils.drawPixel(nW+x,nH-y,nFaceColour)
    end
  end
end
 
-- parse the time into H and M
-- these are stored globally in h and m
local function parseTime()
  local t = os.time()
  local sT = textutils.formatTime(t, true)
 
  local n = string.find(sT, ":")
 
  if(n>0) then
    h = tonumber(string.sub(sT,1,n-1))
    m = tonumber(string.sub(sT,n+1))
  end
end
 
-- plot minute hand
-- it removes the previous minute hand
-- then draws the new one
local function plotM(nW,nH,nX,nY)
  local nTmpColour
 
  if(nOldMX==nX and nOldMY==nY) then
    return
  end
 
  -- remove previous by drawing it again
  -- in the face colour.  Also remove the
  -- hand tag
  if(nOldMX~=-1) then
    if bFaceFilled then
      nTmpColour = nFaceColour
    else
      nTmpColour = nBackColour
    end
    paintutils.drawLine(nW,nH,nOldMX,nOldMY,nTmpColour)
     
    if(bShowMinuteChar) then
      plot(".",nOldMX,nOldMY)
    end
  end
 
  nOldMX = nX
  nOldMY = nY
 
  paintutils.drawLine(nW,nH,nX,nY,nHandColour)
 
  -- draw the centre O
  term.setTextColor(colors.black)
  term.setBackgroundColor(colors.lightGray)
  plot("O",nW,nH)
 
  if(bShowMinuteChar) then
    -- draw the minute hand tag
    plot("M",nX,nY)
  end
end
 
-- plot hour hand
-- it removes the previous hour hand
-- then draws the new one
local function plotH(nW,nH,nX,nY)
  local nTmpColour
  if(nOldHX==nX and nOldHY==nY) then
    return
  end
 
  -- remove previous by drawing it again
  -- in the face colour.  Also remove the
  -- hand tag
  if(nOldHX~=-1) then
    if bFaceFilled then
      nTmpColour = nFaceColour
    else
      nTmpColour = nBackColour
    end
    paintutils.drawLine(nW,nH,nOldHX,nOldHY,nTmpColour)
 
    if(bShowHourChar) then
      plot(".",nOldHX,nOldHY)
    end
  end
 
  nOldHX = nX
  nOldHY = nY
 
  paintutils.drawLine(nW,nH,nX,nY,nHandColour)
 
  if(bShowHourChar) then
    -- draw the hour hand tag
    term.setTextColor(colors.black)
    term.setBackgroundColor(colors.lightGray)
    plot("H",nX,nY)
  end
end
 
local fH
local fM
local fXH
local fYH
local fXM
local fYM
local nyMRadius = nClockRad - (nClockRad / 4)
local nyHRadius = nClockRad - (nClockRad / 2)
 
local nxMRadius = nyMRadius + (nyMRadius / 2)
local nxHRadius = nyHRadius + (nyHRadius / 2)
local nW, nH = term.getSize()
 
nW = (nW / 2)+1
nH = (nH / 2)+1
 
-- plot the circle
face(nW,nH,nClockRad + 1)
 
local bRunning = true
local nTimer
nTimer = os.startTimer(0.25) -- the timer interval to update the clock
 
while(bRunning) do
  parseTime()
 
  fH = (((h % 12)/12) + (m/60/12)) * 360
  fM = (m/60) * 360
 
  -- shift by half a circle
  fH = fH + 180
  fM = fM + 180
 
  fXH = math.sin(math.rad(-fH))
  fYH = math.cos(math.rad(fH))
  fXM = math.sin(math.rad(-fM))
  fYM = math.cos(math.rad(fM))
 
  plotH(nW,nH,  nW + fXH * nxHRadius, nH + fYH * nyHRadius)
  plotM(nW,nH, nW + fXM * nxMRadius, nH + fYM * nyMRadius)
 
  -- draw the ticks on
  ticks(nW,nH)
 
  local event, param1 = os.pullEvent()
 
  while (not (event=="timer" and param1==nTimer) and event~="char") do
    event, param1 = os.pullEvent()
  end
 
  if(event=="char") then
     if(param1=="q" or param1=="Q") then
       bRunning=false
     end
  elseif(event=="timer" and param1==nTimer) then
     -- timer timed out, lets start again
     nTimer = os.startTimer(0.25)
  end
end
 
term.restore()
print("Ended.")
