plan.gno
2.97 Kb · 126 lines
1package penalty
2
3// Mode is what kind of move a keeper plan describes.
4type Mode uint8
5
6const (
7 ModeStar Mode = iota + 1 // spread into a star jump
8 ModeRead // hold and react to the shot
9 ModeDive // commit to a dive
10)
11
12func (m Mode) String() string {
13 switch m {
14 case ModeStar:
15 return "star jump"
16 case ModeRead:
17 return "wait and read"
18 case ModeDive:
19 return "committed dive"
20 }
21 return "unknown"
22}
23
24// Plan is a KeeperPlan decoded with a game's constants.
25type Plan struct {
26 Mode Mode
27 Heading int64 // deci-degrees: 0 = along the line to the taker's right, 1800 = to the left, 900 = straight out
28 Speed int64 // mm/s along the ground (0 for a read)
29 Jump int64 // mm/s upwards (negative = collapse low)
30 Reaction int64 // ms after contact before moving
31}
32
33// Dive headings stay within ForwardMax of the line, except for the middle
34// CentreShare of the direction range, which sweeps the headings in between.
35const (
36 ForwardMax = 220 // deci-degrees
37 CentreShare = 60 // per-mille of the direction range
38 StarMaxSpd = 1150
39)
40
41// DecodePlan maps the four uint16 of a keeper plan onto a move:
42//
43// direction monotonic 0..65535 -> heading 0°..180°
44// speed lowest StarShare -> star jump, next ReadShare -> wait and read,
45// the rest -> dive at DiveMin..DiveMax
46// jump JumpMin..JumpMax
47// reaction ReactionMin..ReactionMax
48//
49// For a read plan the direction and jump are unused: the keeper decides them
50// from the ball.
51func DecodePlan(p KeeperPlan, c Config) Plan {
52 out := Plan{
53 Heading: heading(p.Direction),
54 Jump: span(p.Jump, c.JumpMin, c.JumpMax),
55 Reaction: span(p.Reaction, c.ReactionMin, c.ReactionMax),
56 }
57 u := int64(p.Speed)
58 starEdge := 65535 * c.StarShare / 1000
59 readEdge := 65535 * (c.StarShare + c.ReadShare) / 1000
60 switch {
61 case u < starEdge:
62 out.Mode = ModeStar
63 out.Speed = StarMaxSpd * u / starEdge
64 case u < readEdge:
65 out.Mode = ModeRead
66 default:
67 out.Mode = ModeDive
68 out.Speed = c.DiveMin + (c.DiveMax-c.DiveMin)*(u-readEdge)/maxI(1, 65535-readEdge)
69 }
70 return out
71}
72
73func heading(d uint16) int64 {
74 s := 2*int64(d) - 65535 // -65535 (right) .. 65535 (left)
75 c := int64(65535 * CentreShare / 1000)
76 switch {
77 case s <= -c:
78 return ForwardMax * (65535 + s) / (65535 - c)
79 case s >= c:
80 return 1800 - ForwardMax*(65535-s)/(65535-c)
81 }
82 return 900 + (900-ForwardMax)*s/c
83}
84
85// cosPermille approximates cos(heading) × 1000 (Bhaskara I), heading in
86// deci-degrees within 0..1800. Integer-only, accurate to about 0.2%.
87func cosPermille(h int64) int64 {
88 sign := int64(1)
89 if h > 900 {
90 h = 1800 - h
91 sign = -1
92 }
93 d2 := h * h // (deci-degrees)²
94 return sign * 1000 * (3240000 - 4*d2) / (3240000 + d2)
95}
96
97func maxI(a, b int64) int64 {
98 if a > b {
99 return a
100 }
101 return b
102}
103
104func minI(a, b int64) int64 {
105 if a < b {
106 return a
107 }
108 return b
109}
110
111func absI(a int64) int64 {
112 if a < 0 {
113 return -a
114 }
115 return a
116}
117
118func clampI(x, lo, hi int64) int64 {
119 if x < lo {
120 return lo
121 }
122 if x > hi {
123 return hi
124 }
125 return x
126}