Compare commits
No commits in common. "ef646bf98ebcd42f760b6f6e2b75ff5aea5784d5" and "8629b537b0a4c5b022e4511671f013b56299963b" have entirely different histories.
ef646bf98e
...
8629b537b0
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,2 +0,0 @@
|
|||||||
clock.svg
|
|
||||||
gobytest
|
|
@ -1,113 +0,0 @@
|
|||||||
package clockface
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Point represents a two-dimentional Cartesian coordinate
|
|
||||||
type Point struct {
|
|
||||||
X float64
|
|
||||||
Y float64
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
hourHandLength = 50
|
|
||||||
minuteHandLength = 80
|
|
||||||
secondHandLength = 90
|
|
||||||
clockCentreX = 150
|
|
||||||
clockCentreY = 150
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
secondsInHalfClock = 30
|
|
||||||
secondsInClock = 2 * secondsInHalfClock
|
|
||||||
minutesInHalfClock = 30
|
|
||||||
minutesInClock = 2 * minutesInHalfClock
|
|
||||||
hoursInHalfClock = 6
|
|
||||||
hoursInClock = 2 * hoursInHalfClock
|
|
||||||
)
|
|
||||||
|
|
||||||
const svgStart = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="100%"
|
|
||||||
height="100%"
|
|
||||||
viewBox="0 0 300 300"
|
|
||||||
version="2.0">`
|
|
||||||
|
|
||||||
const bezel = `<circle cx="150" cy="150" r="100" style="fill:#fff;stroke:#000;stroke-width:5px;"/>`
|
|
||||||
|
|
||||||
const svgEnd = `</svg>`
|
|
||||||
|
|
||||||
func SVGWriter(w io.Writer, t time.Time) {
|
|
||||||
io.WriteString(w, svgStart)
|
|
||||||
io.WriteString(w, bezel)
|
|
||||||
secondHand(w, t)
|
|
||||||
minuteHand(w, t)
|
|
||||||
hourHand(w, t)
|
|
||||||
io.WriteString(w, svgEnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SecondHand is the unit vector of the second hand of an analogue clock at the time `t` represented as a Point
|
|
||||||
func secondHand(w io.Writer, t time.Time) {
|
|
||||||
p := secondHandPoint(t)
|
|
||||||
makeHand(w, secondHandLength, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MinuteHand is the unit vector of the minute hand of an analogue clock at the time `t` represented as a Point
|
|
||||||
func minuteHand(w io.Writer, t time.Time) {
|
|
||||||
p := minuteHandPoint(t)
|
|
||||||
makeHand(w, minuteHandLength, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MinuteHand is the unit vector of the minute hand of an analogue clock at the time `t` represented as a Point
|
|
||||||
func hourHand(w io.Writer, t time.Time) {
|
|
||||||
p := hourHandPoint(t)
|
|
||||||
makeHand(w, hourHandLength, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeHand(w io.Writer, length float64, p Point) {
|
|
||||||
p = Point{p.X * length, p.Y * length} // scale
|
|
||||||
p = Point{p.X, -p.Y} // flip
|
|
||||||
p = Point{p.X + clockCentreX, p.Y + clockCentreY} // translate
|
|
||||||
fmt.Fprintf(
|
|
||||||
w,
|
|
||||||
`<line x1="150" y1="150" x2="%f" y2="%f" style="fill:none;stroke:#f00;stroke-width:3px;"/>`,
|
|
||||||
p.X,
|
|
||||||
p.Y,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func hoursInRadians(t time.Time) float64 {
|
|
||||||
return (minutesInRadians(t) / hoursInClock) + math.Pi/(hoursInHalfClock/float64(t.Hour()%hoursInClock))
|
|
||||||
}
|
|
||||||
|
|
||||||
func hourHandPoint(t time.Time) Point {
|
|
||||||
return angleToPoint(hoursInRadians(t))
|
|
||||||
}
|
|
||||||
|
|
||||||
func minutesInRadians(t time.Time) float64 {
|
|
||||||
return (secondsInRadians(t) / minutesInClock) + math.Pi/(minutesInHalfClock/float64(t.Minute()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func minuteHandPoint(t time.Time) Point {
|
|
||||||
return angleToPoint(minutesInRadians(t))
|
|
||||||
}
|
|
||||||
|
|
||||||
func secondsInRadians(t time.Time) float64 {
|
|
||||||
return math.Pi / (secondsInHalfClock / float64(t.Second()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func secondHandPoint(t time.Time) Point {
|
|
||||||
return angleToPoint(secondsInRadians(t))
|
|
||||||
}
|
|
||||||
|
|
||||||
func angleToPoint(angle float64) Point {
|
|
||||||
x := math.Sin(angle)
|
|
||||||
y := math.Cos(angle)
|
|
||||||
|
|
||||||
return Point{x, y}
|
|
||||||
}
|
|
@ -1,123 +0,0 @@
|
|||||||
package clockface
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/xml"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Svg struct {
|
|
||||||
XMLName xml.Name `xml:"svg"`
|
|
||||||
Xmlns string `xml:"xmlns,attr"`
|
|
||||||
Width string `xml:"width,attr"`
|
|
||||||
Height string `xml:"height,attr"`
|
|
||||||
ViewBox string `xml:"viewBox,attr"`
|
|
||||||
Version string `xml:"version,attr"`
|
|
||||||
Circle Circle `xml:"circle"`
|
|
||||||
Line []Line `xml:"line"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Circle struct {
|
|
||||||
Cx float64 `xml:"cx,attr"`
|
|
||||||
Cy float64 `xml:"cy,attr"`
|
|
||||||
R float64 `xml:"r,attr"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Line struct {
|
|
||||||
X1 float64 `xml:"x1,attr"`
|
|
||||||
Y1 float64 `xml:"y1,attr"`
|
|
||||||
X2 float64 `xml:"x2,attr"`
|
|
||||||
Y2 float64 `xml:"y2,attr"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSVGWriterSecondHand(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
time time.Time
|
|
||||||
line Line
|
|
||||||
}{
|
|
||||||
{simpleTime(0, 0, 0), Line{150, 150, 150, 60}},
|
|
||||||
{simpleTime(0, 0, 30), Line{150, 150, 150, 240}},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range cases {
|
|
||||||
t.Run(testName(c.time), func(t *testing.T) {
|
|
||||||
b := bytes.Buffer{}
|
|
||||||
SVGWriter(&b, c.time)
|
|
||||||
|
|
||||||
svg := Svg{}
|
|
||||||
xml.Unmarshal(b.Bytes(), &svg)
|
|
||||||
|
|
||||||
if !containsLine(c.line, svg.Line) {
|
|
||||||
t.Errorf(
|
|
||||||
"Expected to find the second hand line %+v in the SVG lines %v",
|
|
||||||
c.line,
|
|
||||||
svg.Line,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSVGWriterMinuteHand(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
time time.Time
|
|
||||||
line Line
|
|
||||||
}{
|
|
||||||
{simpleTime(0, 0, 0), Line{150, 150, 150, 70}},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range cases {
|
|
||||||
t.Run(testName(c.time), func(t *testing.T) {
|
|
||||||
b := bytes.Buffer{}
|
|
||||||
SVGWriter(&b, c.time)
|
|
||||||
|
|
||||||
svg := Svg{}
|
|
||||||
xml.Unmarshal(b.Bytes(), &svg)
|
|
||||||
|
|
||||||
if !containsLine(c.line, svg.Line) {
|
|
||||||
t.Errorf(
|
|
||||||
"Expected to find the minute hand line %+v in the SVG lines %v",
|
|
||||||
c.line,
|
|
||||||
svg.Line,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSVGWriterHourHand(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
time time.Time
|
|
||||||
line Line
|
|
||||||
}{
|
|
||||||
{simpleTime(6, 0, 0), Line{150, 150, 150, 200}},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range cases {
|
|
||||||
t.Run(testName(c.time), func(t *testing.T) {
|
|
||||||
b := bytes.Buffer{}
|
|
||||||
SVGWriter(&b, c.time)
|
|
||||||
|
|
||||||
svg := Svg{}
|
|
||||||
xml.Unmarshal(b.Bytes(), &svg)
|
|
||||||
|
|
||||||
if !containsLine(c.line, svg.Line) {
|
|
||||||
t.Errorf(
|
|
||||||
"Expected to find the hour hand line %+v in the SVG lines %v",
|
|
||||||
c.line,
|
|
||||||
svg.Line,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func containsLine(l Line, ls []Line) bool {
|
|
||||||
for _, line := range ls {
|
|
||||||
if line == l {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
@ -1,145 +0,0 @@
|
|||||||
package clockface
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestHoursInRadians(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
time time.Time
|
|
||||||
angle float64
|
|
||||||
}{
|
|
||||||
{simpleTime(6, 0, 0), math.Pi},
|
|
||||||
{simpleTime(0, 0, 0), 0},
|
|
||||||
{simpleTime(21, 0, 0), math.Pi * 1.5},
|
|
||||||
{simpleTime(0, 1, 30), math.Pi / ((6 * 60 * 60) / 90)},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range cases {
|
|
||||||
t.Run(testName(c.time), func(t *testing.T) {
|
|
||||||
got := hoursInRadians(c.time)
|
|
||||||
|
|
||||||
if !roughlyEqualFloat64(c.angle, got) {
|
|
||||||
t.Fatalf("Wanted %v radians, but got %v", c.angle, got)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHourHandPoint(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
time time.Time
|
|
||||||
point Point
|
|
||||||
}{
|
|
||||||
{simpleTime(6, 0, 0), Point{0, -1}},
|
|
||||||
{simpleTime(9, 0, 0), Point{-1, 0}},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range cases {
|
|
||||||
t.Run(testName(c.time), func(t *testing.T) {
|
|
||||||
got := hourHandPoint(c.time)
|
|
||||||
if !roughlyEqualPoint(got, c.point) {
|
|
||||||
t.Fatalf("Wanted %v Point, but got %v", c.point, got)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMinutesInRadians(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
time time.Time
|
|
||||||
angle float64
|
|
||||||
}{
|
|
||||||
{simpleTime(0, 30, 0), math.Pi},
|
|
||||||
{simpleTime(0, 0, 7), 7 * (math.Pi / (30 * 60))},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range cases {
|
|
||||||
t.Run(testName(c.time), func(t *testing.T) {
|
|
||||||
got := minutesInRadians(c.time)
|
|
||||||
|
|
||||||
if c.angle != got {
|
|
||||||
t.Fatalf("Wanted %v radians, but got %v", c.angle, got)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMinuteHandPoint(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
time time.Time
|
|
||||||
point Point
|
|
||||||
}{
|
|
||||||
{simpleTime(0, 30, 0), Point{0, -1}},
|
|
||||||
{simpleTime(0, 45, 0), Point{-1, 0}},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range cases {
|
|
||||||
t.Run(testName(c.time), func(t *testing.T) {
|
|
||||||
got := minuteHandPoint(c.time)
|
|
||||||
if !roughlyEqualPoint(got, c.point) {
|
|
||||||
t.Fatalf("Wanted %v Point, but got %v", c.point, got)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSecondsInRadians(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
time time.Time
|
|
||||||
angle float64
|
|
||||||
}{
|
|
||||||
{simpleTime(0, 0, 30), math.Pi},
|
|
||||||
{simpleTime(0, 0, 0), 0},
|
|
||||||
{simpleTime(0, 0, 45), (math.Pi / 2) * 3},
|
|
||||||
{simpleTime(0, 0, 7), (math.Pi / 30) * 7},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range cases {
|
|
||||||
t.Run(testName(c.time), func(t *testing.T) {
|
|
||||||
got := secondsInRadians(c.time)
|
|
||||||
|
|
||||||
if c.angle != got {
|
|
||||||
t.Fatalf("Wanted %v radians, but got %v", c.angle, got)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSecondHandPoint(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
time time.Time
|
|
||||||
point Point
|
|
||||||
}{
|
|
||||||
{simpleTime(0, 0, 30), Point{0, -1}},
|
|
||||||
{simpleTime(0, 0, 45), Point{-1, 0}},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range cases {
|
|
||||||
t.Run(testName(c.time), func(t *testing.T) {
|
|
||||||
got := secondHandPoint(c.time)
|
|
||||||
if !roughlyEqualPoint(got, c.point) {
|
|
||||||
t.Fatalf("Wanted %v Point, but got %v", c.point, got)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func simpleTime(hours, minutes, seconds int) time.Time {
|
|
||||||
return time.Date(321, time.October, 28, hours, minutes, seconds, 0, time.UTC)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testName(t time.Time) string {
|
|
||||||
return t.Format("15:04:05")
|
|
||||||
}
|
|
||||||
|
|
||||||
func roughlyEqualFloat64(a, b float64) bool {
|
|
||||||
const equalityThreshold = 1e-7
|
|
||||||
return math.Abs(a-b) < equalityThreshold
|
|
||||||
}
|
|
||||||
|
|
||||||
func roughlyEqualPoint(a, b Point) bool {
|
|
||||||
return roughlyEqualFloat64(a.X, b.X) && roughlyEqualFloat64(a.Y, b.Y)
|
|
||||||
}
|
|
12
main.go
12
main.go
@ -1,20 +1,14 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"gobytest/clockface"
|
"gobytest/countdown"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// greet.Greet(os.Stdout, "Elodie")
|
// greet.Greet(os.Stdout, "Elodie")
|
||||||
// sleeper := countdown.NewConfigurableSleeper(1*time.Second, time.Sleep)
|
sleeper := countdown.NewConfigurableSleeper(1*time.Second, time.Sleep)
|
||||||
|
|
||||||
// countdown
|
countdown.Countdown(os.Stdout, sleeper)
|
||||||
// countdown.Countdown(os.Stdout, sleeper)
|
|
||||||
|
|
||||||
// clockface
|
|
||||||
t := time.Now()
|
|
||||||
|
|
||||||
clockface.SVGWriter(os.Stdout, t)
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user