seed.gno
3.57 Kb · 134 lines
1package penalty
2
3// Seed is the 32 bytes of entropy that complete a kick.
4//
5// When the player shoots, the seed supplies the computer's keeper and the
6// player's mishit:
7//
8// bytes 0-7 keeper plan: four big-endian uint16 (direction, speed, jump, reaction)
9// bytes 8-31 mishit seed (24 bytes)
10//
11// When the player keeps, the seed supplies the computer's whole kick:
12//
13// bytes 0-1 aim x uint16 -> ±(GoalWidth/2 - ChainAimInset) mm
14// bytes 2-3 aim y uint16 -> ChainAimMinY .. GoalHeight + ChainAimOverY mm
15// bytes 4-5 power uint16 -> ChainPowerMin .. 1000 per-mille
16// byte 6 contact x int8 -> ±ChainContact milli-radii
17// byte 7 contact y int8 -> ±ChainContact milli-radii
18// bytes 8-31 mishit seed (24 bytes)
19//
20// So the layout is the same either way: the first eight bytes are "the
21// computer's move", the last twenty-four are the mishit.
22type Seed [32]byte
23
24// How the computer takes its kicks: inside the frame, aimed a little high to
25// allow for the drop, struck with at least ChainPowerMin.
26const (
27 ChainAimInset = 250 // mm inside each post
28 ChainAimMinY = 300 // mm
29 ChainAimOverY = 200 // mm above the bar at most
30 ChainPowerMin = 450 // per-mille
31 ChainContact = 350 // milli-radii
32)
33
34func u16(b []byte) uint16 { return uint16(b[0])<<8 | uint16(b[1]) }
35
36// span maps a uint16 linearly onto lo..hi.
37func span(u uint16, lo, hi int64) int64 { return lo + (hi-lo)*int64(u)/65535 }
38
39// KeeperPlan reads the computer's keeper plan from bytes 0-7.
40func (s Seed) KeeperPlan() KeeperPlan {
41 return KeeperPlan{
42 Direction: u16(s[0:2]),
43 Speed: u16(s[2:4]),
44 Jump: u16(s[4:6]),
45 Reaction: u16(s[6:8]),
46 }
47}
48
49// Mishit returns bytes 8-31.
50func (s Seed) Mishit() [24]byte {
51 var m [24]byte
52 copy(m[:], s[8:32])
53 return m
54}
55
56// ChainShot reads the computer's kick from bytes 0-7 (see Seed).
57func (s Seed) ChainShot(c Config) ShotInput {
58 halfW := c.GoalWidth/2 - ChainAimInset
59 in := ShotInput{
60 AimX: span(u16(s[0:2]), -halfW, halfW),
61 AimY: span(u16(s[2:4]), ChainAimMinY, c.GoalHeight+ChainAimOverY),
62 Power: span(u16(s[4:6]), ChainPowerMin, 1000),
63 ContactX: (int64(s[6]) - 128) * ChainContact / 128,
64 ContactY: (int64(s[7]) - 128) * ChainContact / 128,
65 }
66 // Keep the contact inside this game's selectable disc.
67 r2 := in.ContactX*in.ContactX + in.ContactY*in.ContactY
68 if m := c.MaxContact; r2 > m*m {
69 r := isqrt(r2)
70 in.ContactX = in.ContactX * m / r
71 in.ContactY = in.ContactY * m / r
72 }
73 return in
74}
75
76const hexDigits = "0123456789abcdef"
77
78// Hex prints the seed as 64 hex characters.
79func (s Seed) Hex() string { return toHex(s[:]) }
80
81func toHex(b []byte) string {
82 out := make([]byte, len(b)*2)
83 for i, v := range b {
84 out[2*i] = hexDigits[v>>4]
85 out[2*i+1] = hexDigits[v&15]
86 }
87 return string(out)
88}
89
90// MishitHex prints a mishit seed as 48 hex characters.
91func MishitHex(m [24]byte) string { return toHex(m[:]) }
92
93// ParseMishitHex reads 48 hex characters.
94func ParseMishitHex(s string) ([24]byte, bool) {
95 var m [24]byte
96 if len(s) != 48 {
97 return m, false
98 }
99 for i := 0; i < 24; i++ {
100 hi, ok1 := hexVal(s[2*i])
101 lo, ok2 := hexVal(s[2*i+1])
102 if !ok1 || !ok2 {
103 return m, false
104 }
105 m[i] = hi<<4 | lo
106 }
107 return m, true
108}
109
110func hexVal(c byte) (byte, bool) {
111 switch {
112 case c >= '0' && c <= '9':
113 return c - '0', true
114 case c >= 'a' && c <= 'f':
115 return c - 'a' + 10, true
116 case c >= 'A' && c <= 'F':
117 return c - 'A' + 10, true
118 }
119 return 0, false
120}
121
122// isqrt is the integer square root (floor).
123func isqrt(n int64) int64 {
124 if n <= 0 {
125 return 0
126 }
127 x := n
128 y := (x + 1) / 2
129 for y < x {
130 x = y
131 y = (x + n/x) / 2
132 }
133 return x
134}