data.gno
15.28 Kb · 631 lines
1package course
2
3import (
4 "errors"
5 "math"
6
7 "gno.land/p/nym-alexiscolin000/gnogolf/physics"
8)
9
10// Magic starts GG1, a hole as one little-endian binary string, frozen with
11// this package: magic, header, field, skin table (str = u8 len + bytes),
12// walls (style runs, then segments and, outside pulses, their Lengths),
13// posts, zones, pulses; each list a count first. Encode is the layout.
14const Magic = "GG1"
15
16// The frozen limits Decode holds a hole to, pulses included.
17const (
18 MaxWalls = 160
19 MaxPosts = 32
20 MaxZones = 32
21 MaxPoly = 64 // points in one polygon
22 MaxPolyTotal = 512 // points in all of them
23 MaxPulses = 16
24 MaxSkins = 64
25 MaxSubsteps = 60
26 MaxTiming = 4096
27 MaxOrder = 999
28 MaxWorld = 16
29)
30
31const (
32 flagRound = 1 << iota
33 flagOutside
34 flagAir
35 flagCapped
36)
37
38// Encode is the hole as GG1. It stores what it is given: call it on a hole
39// after Fit, with its walls prepared.
40func Encode(h *Simple) string {
41 e := &encoder{}
42 f := h.Course
43 if f == nil {
44 f = &physics.Field{}
45 }
46 skins := map[string]int{}
47 var table []string
48 skin := func(s string) {
49 if _, ok := skins[s]; !ok {
50 skins[s] = len(table)
51 table = append(table, s)
52 }
53 }
54 pieces := func(ws []physics.Wall, ps []physics.Post, zs []physics.Zone) {
55 for _, w := range ws {
56 skin(w.Skin)
57 }
58 for _, p := range ps {
59 skin(p.Skin)
60 }
61 for _, z := range zs {
62 skin(z.Skin)
63 }
64 }
65 pieces(f.Walls, f.Posts, f.Zones)
66 for _, p := range h.Pulses {
67 pieces(p.Walls, p.Posts, p.Zones)
68 }
69
70 e.b = append(e.b, Magic...)
71 e.u16(h.W)
72 e.u16(h.H)
73 e.u8(h.Strokes)
74 e.u16(h.Substeps)
75 e.str(h.World)
76 e.f64(h.Order)
77 e.str(h.Title)
78 e.vec(h.Tee)
79 e.vec(h.Pin)
80 e.f64(h.CupRadius)
81 e.f64(h.Shelter)
82 e.f64(f.Friction)
83 e.f64(f.Bounce)
84 e.f64(f.Radius)
85 e.u16(len(table))
86 for _, s := range table {
87 e.str(s)
88 }
89 e.walls(f.Walls, skins, f.Radius, true)
90 e.posts(f.Posts, skins)
91 e.zones(f.Zones, skins)
92 e.u8(len(h.Pulses))
93 for _, p := range h.Pulses {
94 e.i32(p.Every)
95 e.i32(p.On)
96 e.i32(p.Phase)
97 e.walls(p.Walls, skins, f.Radius, false)
98 e.posts(p.Posts, skins)
99 e.zones(p.Zones, skins)
100 }
101 return string(e.b)
102}
103
104type encoder struct{ b []byte }
105
106func (e *encoder) u8(n int) { e.b = append(e.b, byte(n)) }
107func (e *encoder) u16(n int) { e.b = append(e.b, byte(n), byte(n>>8)) }
108func (e *encoder) i32(n int) {
109 u := uint32(int32(n))
110 e.b = append(e.b, byte(u), byte(u>>8), byte(u>>16), byte(u>>24))
111}
112
113func (e *encoder) f64(x float64) {
114 u := math.Float64bits(x)
115 e.b = append(e.b, byte(u), byte(u>>8), byte(u>>16), byte(u>>24), byte(u>>32), byte(u>>40), byte(u>>48), byte(u>>56))
116}
117
118func (e *encoder) vec(v physics.Vec2) {
119 e.f64(v.X)
120 e.f64(v.Y)
121}
122
123func (e *encoder) str(s string) {
124 e.u8(len(s))
125 e.b = append(e.b, s[:len(s)&0xff]...)
126}
127
128// sameStyle is whether two walls share everything but their segment.
129func sameStyle(a, b *physics.Wall) bool {
130 return math.Float64bits(a.Bounce) == math.Float64bits(b.Bounce) && a.Mark == b.Mark && a.Skin == b.Skin &&
131 a.Every == b.Every && a.On == b.On && a.Phase == b.Phase
132}
133
134func (e *encoder) walls(ws []physics.Wall, skins map[string]int, r float64, lengths bool) {
135 e.u16(len(ws))
136 for i := 0; i < len(ws); {
137 j := i + 1
138 for j < len(ws) && sameStyle(&ws[i], &ws[j]) {
139 j++
140 }
141 w := &ws[i]
142 e.u16(j - i)
143 e.f64(w.Bounce)
144 e.i32(int(w.Mark))
145 e.u16(skins[w.Skin])
146 e.i32(w.Every)
147 e.i32(w.On)
148 e.i32(w.Phase)
149 i = j
150 }
151 for _, w := range ws {
152 e.vec(w.Seg.A)
153 e.vec(w.Seg.B)
154 if lengths {
155 l, lp, lm := physics.Lengths(w.Seg, r)
156 e.f64(l)
157 e.f64(lp)
158 e.f64(lm)
159 }
160 }
161}
162
163func (e *encoder) posts(ps []physics.Post, skins map[string]int) {
164 e.u16(len(ps))
165 for _, p := range ps {
166 e.vec(p.C)
167 e.f64(p.R)
168 e.f64(p.Bounce)
169 e.i32(int(p.Mark))
170 e.u16(skins[p.Skin])
171 }
172}
173
174func (e *encoder) zones(zs []physics.Zone, skins map[string]int) {
175 e.u16(len(zs))
176 for _, z := range zs {
177 e.u8(int(z.Kind))
178 flags := 0
179 if z.Round {
180 flags |= flagRound
181 }
182 if z.Outside {
183 flags |= flagOutside
184 }
185 if z.Air {
186 flags |= flagAir
187 }
188 if z.Capped {
189 flags |= flagCapped
190 }
191 e.u8(flags)
192 e.vec(z.Min)
193 e.vec(z.Max)
194 e.vec(z.Vec)
195 e.f64(z.Scale)
196 e.i32(int(z.Mark))
197 e.u16(skins[z.Skin])
198 e.i32(z.Every)
199 e.i32(z.On)
200 e.i32(z.Phase)
201 e.u16(len(z.Poly))
202 for _, p := range z.Poly {
203 e.vec(p)
204 }
205 }
206}
207
208// Decode reads a GG1 hole; anything outside the format, its limits or its
209// value bounds is an error, with nothing returned. It prepares the walls from
210// the stored lengths (physics.PrepareWith) and trusts them: check them once
211// with Exact, or use DecodeChecked, before accepting data from anyone.
212func Decode(s string) (*Simple, error) {
213 d := &decoder{s: s}
214 if len(s) < len(Magic) || s[:len(Magic)] != Magic {
215 return nil, errors.New("course: not GG1 data")
216 }
217 d.i = len(Magic)
218 h := &Simple{}
219 h.W, h.H = d.u16(), d.u16()
220 h.Strokes = d.u8()
221 h.Substeps = d.u16()
222 h.World = d.str()
223 h.Order = d.f64()
224 h.Title = d.str()
225 if d.err != "" {
226 return nil, errors.New(d.err)
227 }
228 if h.W < 1 || h.W > MaxBoard || h.H < 1 || h.H > MaxBoard {
229 return nil, errors.New("course: the board is 1 to 96 a side")
230 }
231 if h.Strokes > 19 {
232 return nil, errors.New("course: par is at most 19")
233 }
234 if h.Substeps < 1 || h.Substeps > MaxSubsteps {
235 return nil, errors.New("course: substeps are 1 to 60")
236 }
237 if !IsWorld(h.World) {
238 return nil, errors.New("course: a world is 1 to 16 letters a-z")
239 }
240 if !(h.Order >= 0 && h.Order <= MaxOrder) {
241 return nil, errors.New("course: order is 0 to 999")
242 }
243 d.w, d.h = float64(h.W), float64(h.H)
244 h.Tee, h.Pin = d.point(), d.point()
245 h.CupRadius, h.Shelter = d.f64(), d.f64()
246 f := &physics.Field{}
247 f.Friction, f.Bounce, f.Radius = d.f64(), d.f64(), d.f64()
248 if d.err == "" {
249 switch {
250 case !(h.CupRadius > 0 && h.CupRadius <= 2):
251 d.fail("the cup's radius is above 0 and at most 2")
252 case !(h.Shelter >= 0 && h.Shelter <= 1):
253 d.fail("shelter is 0 to 1")
254 case !(f.Friction >= 0 && f.Friction < 1):
255 d.fail("friction is 0 to below 1")
256 case !bounce(f.Bounce):
257 d.fail("a bounce is 0 to 1.5")
258 case !(f.Radius >= 0 && f.Radius <= 1):
259 d.fail("the ball's radius is 0 to 1")
260 }
261 }
262 n := d.u16()
263 if n > MaxSkins {
264 d.fail("too many skins")
265 }
266 if d.err != "" {
267 return nil, errors.New(d.err)
268 }
269 d.skins = make([]string, n)
270 for i := range d.skins {
271 d.skins[i] = d.str()
272 }
273 var lens []float64
274 f.Walls, lens = d.walls(true)
275 f.Posts = d.posts()
276 f.Zones = d.zones()
277 np := d.u8()
278 if np > MaxPulses {
279 d.fail("too many pulses")
280 }
281 if d.err != "" {
282 return nil, errors.New(d.err)
283 }
284 if np > 0 {
285 h.Pulses = make([]Pulse, np)
286 }
287 for i := range h.Pulses {
288 p := &h.Pulses[i]
289 p.Every, p.On, p.Phase = d.i32(), d.i32(), d.i32()
290 if !timing(p.Every, p.On, p.Phase) {
291 d.fail("a pulse's timing is out of bounds")
292 }
293 p.Walls, _ = d.walls(false)
294 p.Posts = d.posts()
295 p.Zones = d.zones()
296 if d.err != "" {
297 return nil, errors.New(d.err)
298 }
299 }
300 if d.err != "" {
301 return nil, errors.New(d.err)
302 }
303 if d.i != len(s) {
304 return nil, errors.New("course: trailing bytes after the hole")
305 }
306 physics.PrepareWith(f, lens)
307 h.Course = f
308 return h, nil
309}
310
311// DecodeChecked is Decode, then Exact: for data anyone wrote. It costs a
312// Prepare more; data once checked can be read back with Decode.
313func DecodeChecked(s string) (*Simple, error) {
314 h, err := Decode(s)
315 if err != nil {
316 return nil, err
317 }
318 if !Exact(h) {
319 return nil, errors.New("course: the stored wall lengths are not the walls' own")
320 }
321 return h, nil
322}
323
324// Exact reports whether a decoded hole's stored lengths are its walls' own,
325// its prep bit for bit the one Prepare works out. Use it on a value fresh from
326// Decode: it prepares a copy of the field, which can panic on a hole stored in
327// another realm (the copy keeps that realm's read-only mark).
328func Exact(h *Simple) bool {
329 if h.Course == nil {
330 return false
331 }
332 got := physics.Prepared(h.Course)
333 g := *h.Course
334 physics.Prepare(&g)
335 want := physics.Prepared(&g)
336 if len(got) != len(want) {
337 return false
338 }
339 for i := range want {
340 if math.Float64bits(got[i]) != math.Float64bits(want[i]) {
341 return false
342 }
343 }
344 return true
345}
346
347// decoder reads GG1 with every read bounds-checked: past the end it records
348// the error and reads zeros. Counts are checked against the limits before
349// anything is allocated for them.
350type decoder struct {
351 s string
352 i int
353 err string
354 w, h float64 // the board, for the coordinate bounds
355 skins []string
356 nWalls, nPosts, nZones, nPoints int // running totals, pulses included
357}
358
359func (d *decoder) fail(why string) {
360 if d.err == "" {
361 d.err = "course: " + why
362 }
363}
364
365func (d *decoder) need(n int) bool {
366 if d.i+n > len(d.s) {
367 d.fail("truncated data")
368 d.i = len(d.s)
369 return false
370 }
371 return true
372}
373
374func (d *decoder) u8() int {
375 if !d.need(1) {
376 return 0
377 }
378 d.i++
379 return int(d.s[d.i-1])
380}
381
382func (d *decoder) u16() int {
383 if !d.need(2) {
384 return 0
385 }
386 s, i := d.s, d.i
387 d.i += 2
388 return int(s[i]) | int(s[i+1])<<8
389}
390
391func (d *decoder) i32() int {
392 if !d.need(4) {
393 return 0
394 }
395 s, i := d.s, d.i
396 d.i += 4
397 return int(int32(uint32(s[i]) | uint32(s[i+1])<<8 | uint32(s[i+2])<<16 | uint32(s[i+3])<<24))
398}
399
400// f64 reads a float and refuses NaN and the infinities; -0 is kept.
401func (d *decoder) f64() float64 {
402 s, i := d.s, d.i
403 if i+8 > len(s) {
404 d.need(8)
405 return 0
406 }
407 d.i = i + 8
408 x := math.Float64frombits(uint64(s[i]) | uint64(s[i+1])<<8 | uint64(s[i+2])<<16 | uint64(s[i+3])<<24 |
409 uint64(s[i+4])<<32 | uint64(s[i+5])<<40 | uint64(s[i+6])<<48 | uint64(s[i+7])<<56)
410 if x-x != 0 { // NaN or an infinity
411 d.fail("a number is not finite")
412 return 0
413 }
414 return x
415}
416
417func (d *decoder) str() string {
418 n := d.u8()
419 if !d.need(n) {
420 return ""
421 }
422 d.i += n
423 return d.s[d.i-n : d.i]
424}
425
426// point reads a position, which must lie on the board or within 1 of it.
427func (d *decoder) point() physics.Vec2 {
428 x, y := d.f64(), d.f64()
429 if !(x >= -1 && x <= d.w+1 && y >= -1 && y <= d.h+1) {
430 d.fail("a point lies off the board")
431 }
432 return physics.Vec2{X: x, Y: y}
433}
434
435// corner reads a corner of a zone's box, which may lie up to MaxBoard off the
436// board: Fit keeps a round or polygon zone's box whole.
437func (d *decoder) corner() physics.Vec2 {
438 x, y := d.f64(), d.f64()
439 if !(x >= -MaxBoard && x <= d.w+MaxBoard && y >= -MaxBoard && y <= d.h+MaxBoard) {
440 d.fail("a zone's box lies far off the board")
441 }
442 return physics.Vec2{X: x, Y: y}
443}
444
445func (d *decoder) skin() string {
446 k := d.u16()
447 if k >= len(d.skins) {
448 d.fail("a skin index is out of range")
449 return ""
450 }
451 return d.skins[k]
452}
453
454func bounce(b float64) bool { return b >= 0 && b <= 1.5 }
455
456// timing is the bounds of Every/On/Phase: untimed is all zeros; timed, on at
457// most every and phase below it.
458func timing(every, on, phase int) bool {
459 if every == 0 {
460 return on == 0 && phase == 0
461 }
462 return every > 0 && every <= MaxTiming && on >= 0 && on <= every && phase >= 0 && phase < every
463}
464
465// IsWorld is whether w can name a world: 1 to MaxWorld letters a-z, as Decode
466// holds data to.
467func IsWorld(w string) bool {
468 if len(w) < 1 || len(w) > MaxWorld {
469 return false
470 }
471 for i := 0; i < len(w); i++ {
472 if w[i] < 'a' || w[i] > 'z' {
473 return false
474 }
475 }
476 return true
477}
478
479// walls reads a wall list: its style runs, then its segments (and, with
480// lengths, each wall's three Lengths).
481func (d *decoder) walls(lengths bool) ([]physics.Wall, []float64) {
482 n := d.u16()
483 if d.nWalls += n; d.nWalls > MaxWalls {
484 d.fail("too many walls")
485 }
486 if d.err != "" {
487 return nil, nil
488 }
489 ws := make([]physics.Wall, n)
490 for i := 0; i < n && d.err == ""; {
491 count := d.u16()
492 if count < 1 || count > n-i {
493 d.fail("a wall style run does not fit the walls")
494 break
495 }
496 w := physics.Wall{Bounce: d.f64(), Mark: rune(d.i32()), Skin: d.skin(), Every: d.i32(), On: d.i32(), Phase: d.i32()}
497 if !bounce(w.Bounce) {
498 d.fail("a bounce is 0 to 1.5")
499 }
500 if !timing(w.Every, w.On, w.Phase) {
501 d.fail("a wall's timing is out of bounds")
502 }
503 for k := 0; k < count; k++ {
504 ws[i+k] = w
505 }
506 i += count
507 }
508 // one bounds check, then le per float: most of a hole's floats are here
509 size := 32
510 if lengths {
511 size = 56
512 }
513 if d.err != "" || !d.need(n*size) {
514 return nil, nil
515 }
516 var lens []float64
517 if lengths {
518 lens = make([]float64, 3*n)
519 }
520 s, i := d.s, d.i
521 for k := range ws {
522 ax, ay, bx, by := le(s, i), le(s, i+8), le(s, i+16), le(s, i+24)
523 if !(ax >= -1 && ax <= d.w+1 && ay >= -1 && ay <= d.h+1 && bx >= -1 && bx <= d.w+1 && by >= -1 && by <= d.h+1) {
524 d.fail("a point lies off the board")
525 return nil, nil
526 }
527 ws[k].Seg = physics.Segment{A: physics.Vec2{X: ax, Y: ay}, B: physics.Vec2{X: bx, Y: by}}
528 if lengths {
529 l, lp, lm := le(s, i+32), le(s, i+40), le(s, i+48)
530 if l-l != 0 || lp-lp != 0 || lm-lm != 0 {
531 d.fail("a number is not finite")
532 return nil, nil
533 }
534 lens[3*k], lens[3*k+1], lens[3*k+2] = l, lp, lm
535 }
536 i += size
537 }
538 d.i = i
539 return ws, lens
540}
541
542// le is the little-endian float at s[i:i+8]; the caller checked the length.
543// f64 has it inline: a call more per float is gas on every decode.
544func le(s string, i int) float64 {
545 return math.Float64frombits(uint64(s[i]) | uint64(s[i+1])<<8 | uint64(s[i+2])<<16 | uint64(s[i+3])<<24 |
546 uint64(s[i+4])<<32 | uint64(s[i+5])<<40 | uint64(s[i+6])<<48 | uint64(s[i+7])<<56)
547}
548
549func (d *decoder) posts() []physics.Post {
550 n := d.u16()
551 if d.nPosts += n; d.nPosts > MaxPosts {
552 d.fail("too many posts")
553 }
554 if d.err != "" {
555 return nil
556 }
557 ps := make([]physics.Post, n)
558 for i := 0; i < n && d.err == ""; i++ {
559 p := &ps[i]
560 p.C = d.point()
561 p.R, p.Bounce = d.f64(), d.f64()
562 p.Mark, p.Skin = rune(d.i32()), d.skin()
563 if !(p.R > 0 && p.R <= 8) {
564 d.fail("a post's radius is above 0 and at most 8")
565 }
566 if !bounce(p.Bounce) {
567 d.fail("a bounce is 0 to 1.5")
568 }
569 }
570 return ps
571}
572
573func (d *decoder) zones() []physics.Zone {
574 n := d.u16()
575 if d.nZones += n; d.nZones > MaxZones {
576 d.fail("too many zones")
577 }
578 if d.err != "" {
579 return nil
580 }
581 zs := make([]physics.Zone, n)
582 for i := 0; i < n && d.err == ""; i++ {
583 z := &zs[i]
584 kind, flags := d.u8(), d.u8()
585 if kind > int(physics.Loop) {
586 d.fail("a zone's kind is unknown")
587 }
588 if flags&^(flagRound|flagOutside|flagAir|flagCapped) != 0 {
589 d.fail("a zone has unknown flags")
590 }
591 z.Kind = physics.ZoneKind(kind)
592 z.Round, z.Outside, z.Air, z.Capped = flags&flagRound != 0, flags&flagOutside != 0, flags&flagAir != 0, flags&flagCapped != 0
593 z.Min, z.Max = d.corner(), d.corner()
594 switch z.Kind {
595 case physics.Tunnel, physics.Hazard, physics.Loop:
596 z.Vec = d.point() // a place on the board
597 default:
598 z.Vec = physics.Vec2{X: d.f64(), Y: d.f64()} // a push
599 // no steeper than MaxSin (squared: no root; a NaN fails it)
600 if max := physics.G * physics.MaxSin; !(z.Vec.X*z.Vec.X+z.Vec.Y*z.Vec.Y <= max*max) {
601 d.fail("a slope pushes at most G·MaxSin (0.95)")
602 }
603 }
604 z.Scale = d.f64()
605 if !(z.Scale >= 0 && z.Scale <= 8) {
606 d.fail("a zone's scale is 0 to 8")
607 }
608 z.Mark, z.Skin = rune(d.i32()), d.skin()
609 z.Every, z.On, z.Phase = d.i32(), d.i32(), d.i32()
610 if !timing(z.Every, z.On, z.Phase) {
611 d.fail("a zone's timing is out of bounds")
612 }
613 np := d.u16()
614 if np == 1 || np == 2 || np > MaxPoly {
615 d.fail("a polygon has 3 to 64 points")
616 }
617 if d.nPoints += np; d.nPoints > MaxPolyTotal {
618 d.fail("too many polygon points")
619 }
620 if d.err != "" {
621 break
622 }
623 if np > 0 {
624 z.Poly = make([]physics.Vec2, np)
625 for k := range z.Poly {
626 z.Poly[k] = d.point()
627 }
628 }
629 }
630 return zs
631}