fuzzyworld

nordic-skating / local-littlejs / files / test / cracks.test.ts

Branch local-littlejs · default branch · all branches

cracks.test.ts

3.6 KB · history · blame

1import { describe, expect, it } from 'vitest'
2import { type Point, crackNetwork, crackStar } from '../src/ice/cracks.js'
3import { mulberry32 } from '../src/world/lake.js'
4
5const SIZE = 32
6const network = crackNetwork(mulberry32(2018), SIZE)
7
8/** Whether the stretch a-b crosses the stretch c-d somewhere inside both, not at an end of either. */
9function crosses(a: Point, b: Point, c: Point, d: Point): boolean {
10 const side = (p: Point, q: Point, r: Point): number => (q[0] - p[0]) * (r[1] - p[1]) - (q[1] - p[1]) * (r[0] - p[0])
11 const eps = 1e-6
12 return side(a, b, c) * side(a, b, d) < -eps && side(c, d, a) * side(c, d, b) < -eps
13}
14
15describe('cracks', () => {
16 it('are the same cracks for the same numbers', () => {
17 expect(crackNetwork(mulberry32(2018), SIZE)).toEqual(network)
18 expect(crackNetwork(mulberry32(2019), SIZE)).not.toEqual(network)
19 })
20
21 it('are a few long ones, more between them, and many hairlines', () => {
22 const length = (points: Point[]): number =>
23 points.slice(1).reduce((sum, p, i) => sum + Math.hypot(p[0] - points[i]![0], p[1] - points[i]![1]), 0)
24 const of = (rank: number): number[] => network.filter((c) => c.rank === rank).map((c) => length(c.points))
25 expect(of(0).length).toBe(5)
26 expect(of(1).length).toBeGreaterThan(15)
27 expect(of(2).length).toBeGreaterThan(25)
28 const mean = (lengths: number[]): number => lengths.reduce((a, b) => a + b) / lengths.length
29 expect(mean(of(0))).toBeGreaterThan(15)
30 expect(mean(of(1))).toBeLessThan(mean(of(0)))
31 expect(mean(of(2))).toBeLessThan(mean(of(1)))
32 })
33
34 it('run nearly straight', () => {
35 for (const { points, rank } of network.filter((c) => c.rank < 2 && c.points.length > 20)) {
36 const end = points[points.length - 1]!
37 const straight = Math.hypot(end[0] - points[0]![0], end[1] - points[0]![1])
38 const along = (points.length - 1) * 0.25
39 // As the crow flies, most of the way it actually runs.
40 expect(straight / along).toBeGreaterThan(rank === 0 ? 0.8 : 0.6)
41 }
42 })
43
44 it('stop at one another and never cross', () => {
45 // Every stretch of every crack, with which crack it is of; and the sheet repeats, so
46 // each is tried against the others' copies in the sheets round it as well.
47 const stretches = network.flatMap((crack, which) =>
48 crack.points.slice(1).map((p, i) => ({ a: crack.points[i]!, b: p, which })))
49 let crossings = 0
50 for (const one of stretches) {
51 for (const other of stretches) {
52 if (other.which <= one.which) continue
53 for (const ox of [-SIZE, 0, SIZE]) {
54 for (const oy of [-SIZE, 0, SIZE]) {
55 if (crosses(one.a, one.b, [other.a[0] + ox, other.a[1] + oy], [other.b[0] + ox, other.b[1] + oy])) crossings++
56 }
57 }
58 }
59 }
60 // A later crack may be run through by nothing; an earlier one may, at the very start
61 // of a crack leaving it, be touched. Crossings proper there are none of, or next to.
62 expect(crossings).toBeLessThanOrEqual(3)
63 })
64
65 it('open as a star of arms from one point, in order outwards', () => {
66 const star = crackStar(mulberry32(7), 6)
67 expect(star.length).toBeGreaterThan(20)
68 expect(star.filter((s) => s.along === 0 && s.from[0] === 0 && s.from[1] === 0).length).toBeGreaterThanOrEqual(2)
69 let furthest = 0
70 for (const { from, to, along } of star) {
71 expect(Math.hypot(to[0] - from[0], to[1] - from[1])).toBeCloseTo(0.25, 5)
72 expect(along).toBeGreaterThanOrEqual(0)
73 furthest = Math.max(furthest, Math.hypot(to[0], to[1]))
74 }
75 expect(furthest).toBeGreaterThan(2)
76 expect(furthest).toBeLessThan(7)
77 })
78})