featured.gno
3.82 Kb · 145 lines
1package golf
2
3import (
4 "strconv"
5 "time"
6)
7
8// The records the hub puts forward (as the site's boards, web/lib/featured.ts,
9// whose draw and rules these are): not the course ranking's top three, a few
10// players worth racing today, each for a reason said beside them. One a
11// player, drawn again each day (the same day, the same picks as the site's,
12// but for the players the site's bot check hides: the chain knows none).
13
14// featuredRead is how much of the course ranking the picks look at: its first
15// page; podium, its first places, which get no card of their own.
16const (
17 featuredRead = 50
18 podium = 3
19)
20
21// pick is a player put forward, and why.
22type pick struct {
23 r *row
24 name string
25 at int // their place on the ranking
26 tag string
27}
28
29// today is the day, a number: the picks hold for it and change with it (block time).
30func today() int64 { return time.Now().Unix() / 86400 }
31
32// draw is a player's draw for a day: FNV-1a over their address, seeded with
33// the day (featured.ts draw: the same numbers, so the same picks).
34func draw(player string, day int64) uint32 {
35 h := uint32(2166136261) ^ uint32(day)
36 for i := 0; i < len(player); i++ {
37 h = (h ^ uint32(player[i])) * 16777619
38 }
39 return h
40}
41
42// featured is up to n picks from the course ranking in mode m: a fast start (a
43// third of the course or less, at par or better), the best average against par
44// (a third of the course played, nine holes at least), and today's picks for
45// the rest; never the first three places.
46func featured(m, n int) []pick {
47 // every named row read, the first three too (the best average is the
48 // course's, as featured.ts takes it, whoever holds it), numbered as the
49 // boards number them: a row whose name was deleted keeps no place
50 var rows []pick
51 walked, at := 0, 0
52 ranks[m].Iterate("", "", func(_, p string) bool {
53 walked++
54 if name := nameOf(p); name != "" {
55 at++
56 rows = append(rows, pick{r: rowOf(m, p), name: name, at: at})
57 }
58 return walked >= featuredRead
59 })
60 course := slots.Size()
61 third := course / 3
62 perHole := func(r *row) float64 { return float64(r.strokes-r.par) / float64(r.holes) }
63 var rising, many []pick
64 for _, x := range rows {
65 if x.r.holes > 0 && x.r.holes <= max1(third) && x.r.strokes <= x.r.par {
66 rising = append(rising, x)
67 }
68 if x.r.holes >= nine(third) {
69 many = append(many, x)
70 }
71 }
72 var sharp []pick
73 for _, x := range many {
74 switch {
75 case len(sharp) == 0 || perHole(x.r) < perHole(sharp[0].r):
76 sharp = []pick{x}
77 case perHole(x.r) == perHole(sharp[0].r):
78 sharp = append(sharp, x)
79 }
80 }
81 day := today()
82 var out []pick
83 used := map[string]bool{}
84 take := func(from []pick, tag string, again bool) {
85 for len(out) < n {
86 best := -1
87 for i, x := range from {
88 if !used[x.r.player] && x.at > podium && (best < 0 || draw(x.r.player, day) < draw(from[best].r.player, day)) {
89 best = i
90 }
91 }
92 if best < 0 {
93 return
94 }
95 x := from[best]
96 used[x.r.player] = true
97 x.tag = tag
98 if tag == "Today's pick" && countTag(out, tag) > 0 {
99 x.tag = "Also today"
100 }
101 out = append(out, x)
102 if !again {
103 return
104 }
105 }
106 }
107 take(rising, "Fast start", false)
108 take(sharp, "Best average", false)
109 take(rows, "Today's pick", true)
110 return out
111}
112
113func countTag(ps []pick, tag string) int {
114 n := 0
115 for _, p := range ps {
116 if p.tag == tag {
117 n++
118 }
119 }
120 return n
121}
122
123func max1(n int) int {
124 if n < 1 {
125 return 1
126 }
127 return n
128}
129
130func nine(n int) int {
131 if n < 9 {
132 return 9
133 }
134 return n
135}
136
137// featuredRows is the hub's records to beat in mode m, as markdown lines.
138func featuredRows(m int) string {
139 s := ""
140 for _, x := range featured(m, 3) {
141 s += "- **" + x.tag + ":** " + md("@"+x.name, "/u/"+x.name) + ", " + count(x.r.holes, "hole") + " at " + vsPar(x.r.strokes-x.r.par) +
142 " (#" + strconv.Itoa(x.at) + ") · " + md("race their ghosts", playURL+"?by="+x.r.player) + "\n"
143 }
144 return s
145}