By request the countdown time source code. I use this to time breaks on the radio show because they’re hard timed - i.e. the network requires me to start and stop exactly on the second for every break.
First the Racket source (requires DrRacket):
Here are the binaries:
This is the hot clock illustration referred to in the source code:
And here’s the source if you just want to read it without installing Racket. (Excuse the verbose type comments - that’s the style promoted in HtDP and in the fabulous How To Code classes from Gregor Kiczales at UBC based on HtDP.)
;; Show Time
;; a simple countdown timer for the Tech Guy Radio Show
(require 2htdp/universe)
(require 2htdp/image)
; Design
;
; ShowTime is an onscreen prompt for radio show hosts.
;
; The display is based on the traditional "hotclock" used by radio
; stations. The clock represents an hour of programming time with a
; circle divided into wedges representing various kinds of programming
; elements.
;
;
; The wedges can represent any arbitrary number of programming elements,
; but from the host's point of view the only things that matter are
; when to start talking (gray on the clock above), and when to stop
; (green and white on the clock above).
;
; This program features a large timer that counts down the number of
; seconds until the next transition: break to show and show to break.
; For quick comprehension the program uses four colored backgrounds for
; the timer:
;
; Green - in show, more than two minutes remain
; Yellow - in show, segment will end in under 2 minutes
; Red - in show, segment will end in under 30 seconds
; Blue - in break (ads, promos, news, etc.)
;
; The transition times for my Tech Guy Radio show are as follows (in seconds):
;
; Break1 0-344
; Show1 345-899
; Break2 900-1164
; Show2 1165-1679
; Break3 1680-1984
; Show3 1985->2519
; Break4 2520->2784
; Show4 2785-3409
; Break5 3410-3599
;
;; --------------------
;; Constants
;; --------------------
; types of segments
(define BREAK "BREAK")
(define SHOW "SHOW")
(define WRAP-TIME 120) ; <2 minutes remaining, yellow background
(define OUT-TIME 30) ; <30 seconds remaining, red background
(define SECS-IN-HOUR (* 60 60)) ; number of seconds in an hour (0-3600]
;; Image constants
; window info
(define WIDTH 750) ; width of window
(define HEIGHT 300) ; height of window
(define WINDOW-TITLE "Showtime") ; Window name
; countdown text
(define TEXT-SIZE 255)
(define TEXT-COLOR "black")
(define FACE "Fira Code")
(define FAMILY 'default)
(define STYLE 'normal)
(define WEIGHT 'bold)
(define UNDERLINE? #false)
(define TEXT-X (/ WIDTH 2)) ; where to place the text in window (center)
(define TEXT-Y (/ HEIGHT 2))
; background colors
(define BREAK-COLOR "lightblue")
(define SHOW-COLOR "green")
(define WRAP-COLOR "yellow")
(define OUT-COLOR "red")
;; --------------------
;; Data
;; --------------------
(define-struct segment (end type color))
;; Segment is (make-segment Integer String String)
;; interp. a segment of a show clock defined
;; by segment end time in seconds, type, and background color
(define SEG1 (make-segment 350 BREAK BREAK-COLOR))
(define SEG2 (make-segment 915 SHOW SHOW-COLOR))
(define SEG3 (make-segment 1165 BREAK BREAK-COLOR))
(define SEG4 (make-segment 1685 SHOW SHOW-COLOR))
(define SEG5 (make-segment 1970 BREAK BREAK-COLOR))
(define SEG6 (make-segment 2520 SHOW SHOW-COLOR))
(define SEG7 (make-segment 2785 BREAK BREAK-COLOR))
(define SEG8 (make-segment 3415 SHOW SHOW-COLOR))
(define SEG9 (make-segment 3599 BREAK BREAK-COLOR))
#;
(define (fn-for-Segment seg)
(... (segment-end seg) ; Integer(0-3600]
(segment-type seg) ; String
(segment-color seg))) ; String
;; Hotclock is one of:
;; - empty
;; - (cons segment (ListOfSegment)
;; interp. a list of Segments
;; assume segments are sorted by increasing end time
(define clock (list SEG1 SEG2 SEG3 SEG4 SEG5 SEG6 SEG7 SEG8 SEG9))
#;
(define (fn-for-hotclock los)
(cond [(empty? los) (...)] ;BASE CASE
[else (... (first los) ;Segment
(fn-for-hotclock (rest los)))])) ;NATURAL RECURSION
(define-struct displayState (bgd countdown))
;; DisplayState is (make-displayState String Integer)
;; interp. the current state of the world reflecting:
;; background color and countdown clock time
(define DS0 (make-displayState "black" 0))
(define DS1 (make-displayState BREAK-COLOR 200))
#;
(define (fn-for-displayState ds)
(... (displayState-bgd ds) ; Current background color
(displayState-countdown ds))) ; Current countdown time
;; --------------------
;; Functions
;; --------------------
;; displayState -> displayState
;; the event handler for ShowTime
;; called every 1 second
(define (main displayState)
(big-bang displayState ; infinite loop, passing displayState to each handler in turn (provided by Racket)
(on-tick updateState 1) ; tock updates the displayState once a second
(to-draw renderState) ; draw the current displayState on the screen
(name WINDOW-TITLE)))
;; displayState -> displayState
;; creates next display state
(define (updateState displayState)
(local [(define sec (modulo (current-seconds) SECS-IN-HOUR)) ; current second in hour (0-3600]
(define seg (current-segment sec clock))] ; current segment
(make-displayState (get-color sec seg) ; current background color
(get-countdown sec seg)))) ; seconds left until next transition
;; displayState -> Image
;; draws the current show state
; (define (renderState displayState) empty-image) ; stub
(define (renderState displayState)
(place-image
(text/font (secs->min:sec (displayState-countdown displayState))
TEXT-SIZE TEXT-COLOR FACE FAMILY STYLE WEIGHT UNDERLINE?)
TEXT-X TEXT-Y
(empty-scene WIDTH HEIGHT (displayState-bgd displayState))))
;; Integer Hotclock -> Segment
;; given the current time and a hotclock produces the current segment
(check-expect (current-segment 300 clock) SEG1)
(check-expect (current-segment 1600 clock) SEG4)
(check-expect (current-segment 2700 clock) SEG7)
; (define (current-segment sec clock) SEG1) ; stub
(define (current-segment sec clock)
(cond [(empty? clock) (error (string-append "current-segment: time out of range " (number->string sec)))]
[else (if (<= sec (segment-end (first clock)))
(first clock)
(current-segment sec (rest clock)))]))
;; Integer Segment -> String
;; given the time and current segment, produces the current background color
(check-expect (get-color 200 SEG1) BREAK-COLOR)
(check-expect (get-color 230 SEG1) BREAK-COLOR)
(check-expect (get-color 340 SEG1) BREAK-COLOR)
(check-expect (get-color 600 SEG2) SHOW-COLOR)
(check-expect (get-color 815 SEG2) WRAP-COLOR)
(check-expect (get-color 890 SEG2) OUT-COLOR)
; (define (get-color sec seg) BREAK-COLOR) ; stub
(define (get-color sec seg)
(cond [(> sec (segment-end seg)) (error "Get-color: Countdown and Segment mismatch")]
[(equal? (segment-type seg) BREAK) BREAK-COLOR] ; we're in a break
[(< sec (- (segment-end seg) WRAP-TIME)) SHOW-COLOR] ; in a show, but not wrapping
[(< sec (- (segment-end seg) OUT-TIME)) WRAP-COLOR] ; wrapping
[else OUT-COLOR])) ; has to be last 30 seconds of segment
;; Integer Segment -> Integer
;; produces the current countdown given current time and segment
;; (we need this function to handle the exceptional case at the end of the hour
;; we want the countdown to include length of break segments at end of previous hour plus beginning of next hour
(check-expect (get-countdown 200 SEG1) (- (segment-end SEG1) 200))
(check-expect (get-countdown 3500 SEG9) (+ (- (segment-end SEG9) 3500) (segment-end SEG1)))
; (define (get-countdown sec seg) 0) ; stub
(define (get-countdown sec seg)
(+ (- (segment-end seg) sec)
(if (equal? seg (first (reverse clock))) ; in the last segment of the hour?
(segment-end (first clock)) ; if so add the seconds in the first seg of the next hour
0))) ; otherwise make no adjustment
;; Integer -> String
;; produces a string in the form of min:sec from seconds
(check-expect (secs->min:sec 120) "2:00")
(check-expect (secs->min:sec 494) "8:14")
(check-expect (secs->min:sec 30) ":30")
(check-expect (secs->min:sec 60) "1:00")
; (define (secs->min:sec secs) "1:00") ;stub
(define (secs->min:sec secs)
(local [(define m (quotient secs 60))
(define s (modulo secs 60))]
(format "~a:~a"
(if (zero? m)
""
m)
(if (< s 10)
(string-append "0" (number->string s))
s))))
(main DS0)