Telling the time based on the "set theory principle", the Mengenlehreuhr consists of 24 lights which are divided into one circular blinking yellow light on top to denote the seconds, two top rows denoting the hours and two bottom rows denoting the minutes.
As a morning exercise I have been writing an implementation of Mengenlehreuhr in Python using curses
.
The source of this is as follows:
import curses
from datetime import datetime
from math import floor
class Mengenlehreuhr():
screen = None
panel_name = [
'seconds', 'five_hours', 'hours', 'five_minutes', 'minutes'
]
panes = {
'seconds': [None],
'five_hours': [None, None, None, None],
'hours': [None, None, None, None],
'five_minutes': [
None, None, None, None,
None, None, None, None,
None, None, None
],
'minutes': [None, None, None, None]
}
def __init__(self):
self.screen = curses.initscr()
curses.noecho()
curses.cbreak()
self.screen.keypad(1)
curses.start_color()
curses.use_default_colors()
curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK)
curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_RED)
curses.init_pair(3, curses.COLOR_WHITE, curses.COLOR_YELLOW)
curses.curs_set(0)
def close(self):
curses.nocbreak()
self.screen.keypad(0)
curses.echo()
curses.endwin()
def create_clock_face(self):
offset = 0
screen_h, screen_w = self.screen.getmaxyx()
screen_w = int(floor(screen_w / 2))
for i in range(5):
j = 0
k = self.panel_name[i]
width = 11
for p in range(len(self.panes[k])):
if k == 'seconds':
x = screen_w - int(floor(width / 2))
self.panes[k][p] = self.create_window(x, offset)
elif k == 'minutes' or k == 'hours' or k == 'five_hours':
x = screen_w - int(floor((len(self.panes[k]) * width) / 2))
i = (j * 11)
self.panes[k][p] = self.create_window(x + i, offset, width=10)
elif k == 'five_minutes':
width = 4
x = screen_w - int(floor((len(self.panes[k]) * width) / 2))
i = 0 + (j * width)
self.panes[k][p] = self.create_window(x + i, offset, width=3)
j += 1
offset = offset + 5
def create_window(self, x, y, height = 4, width = 11):
win = curses.newwin(height, width, y, x)
win.box()
win.refresh()
return win
def update(self, time):
for i in range(5):
k = self.panel_name[i]
color_pair = curses.color_pair(1)
for p in range(len(self.panes[k])):
if k == 'seconds':
color_pair = curses.color_pair(2) if time.second % 2 == 0 else curses.color_pair(3)
elif k == 'minutes':
color_pair = curses.color_pair(3) if p < time.minute % 5 else curses.color_pair(1)
elif k == 'hours':
color_pair = curses.color_pair(2) if p < time.hour % 5 else curses.color_pair(1)
elif k == 'five_hours':
hours = int(floor(time.hour / 5))
color_pair = curses.color_pair(2) if p < hours else curses.color_pair(1)
elif k == 'five_minutes':
minutes = int(floor(time.minute / 5))
if p in (2, 5, 8) and p < minutes:
color_pair = curses.color_pair(2)
else:
color_pair = curses.color_pair(3) if p < minutes else curses.color_pair(1)
self.panes[k][p].bkgd(' ', color_pair)
self.panes[k][p].refresh()
def run(self):
self.create_clock_face()
while True:
try:
self.update(datetime.now())
except KeyboardInterrupt:
self.close()
exit(0)
clock = Mengenlehreuhr()
clock.run()
In particular I'd be interested in seeing a better solution for the following part of the update
statement which I am sure can be improved:
if p in (2, 5, 8) and p < minutes:
color_pair = curses.color_pair(2)
else:
color_pair = curses.color_pair(3) if p < minutes else curses.color_pair(1)
Personally I'd prefer not to have the p in (2, 5, 8)
if possible but write this a different way which doesn't involve "magic numbers".
Secondly, is there a way of referencing the default terminal colours rather than having curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK)
as the "off" lamp?
Finally, there seems to be an inefficiency in the code as the seconds lamp occasionally gets stuck. What can be done to overcome this and make it more accurate?