Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}