-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpool.ts
More file actions
99 lines (91 loc) · 3.89 KB
/
Copy pathpool.ts
File metadata and controls
99 lines (91 loc) · 3.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/**
* 8-Ball / Pool (game = pool, pool2, pool3). Reference game module.
*
* replay = "seg0|seg1..." — seg0 carries the shot params (`&d:&x:&y:&p:&s:`) and
* seg1 the resulting layout; ball blocks use '#' between records, but the exact
* leading/trailing '#' placement varies, so it's captured verbatim in `_fmt` and
* reproduced byte-exact. ball record = "x,y,rot,alpha,num,vx,vy,vz"
* (alpha<1 => pocketed; num 0=cue, 1-7 solids, 8=eight, 9-15 stripes).
*
* state = { shot:{d,x,y,power,spin}, segments:Ball[][], initial:Ball[],
* final:Ball[], stripes:number, _fmt:{...} }
*/
import { register } from "../registry.ts";
const f6 = (n: number) => (Object.is(n, -0) ? "-0.000000" : n.toFixed(6));
function ball(rec: string) {
const p = rec.split(",");
return {
x: +p[0], y: +p[1], rot: +p[2], alpha: +p[3], num: Math.trunc(+p[4]),
vx: +p[5], vy: +p[6], vz: +p[7], pocketed: +p[3] < 1.0,
};
}
type Ball = ReturnType<typeof ball>;
const ballStr = (b: Ball) =>
`${f6(b.x)},${f6(b.y)},${f6(b.rot)},${f6(b.alpha)},${Math.trunc(b.num)},${f6(b.vx)},${f6(b.vy)},${f6(b.vz)}`;
interface Seg { head: string; has_balls: boolean; lead: boolean; records: string[]; trail: boolean; rest: string; }
function splitSeg(seg: string): Seg {
const i = seg.indexOf("balls:");
if (i < 0) return { head: seg, has_balls: false, lead: false, records: [], trail: false, rest: "" };
const head = seg.slice(0, i);
const region = seg.slice(i + "balls:".length);
const amp = region.indexOf("&");
const ballsStr = amp >= 0 ? region.slice(0, amp) : region;
const rest = amp >= 0 ? region.slice(amp) : "";
const toks = ballsStr.split("#");
return {
head, has_balls: true,
lead: toks.length > 0 && toks[0] === "",
records: toks.filter((t) => t !== ""),
trail: toks.length > 0 && toks[toks.length - 1] === "",
rest,
};
}
function joinSeg(f: Seg, records: string[]): string {
if (!f.has_balls) return f.head;
return f.head + "balls:" + (f.lead ? "#" : "") + records.join("#") + (f.trail ? "#" : "") + f.rest;
}
export function parse(replay: string, _env: Record<string, string>) {
const segs = replay.split("|");
const shot = { d: 0, x: 0, y: 0, power: 0, spin: 0 };
for (const tok of segs[0].split("balls:")[0].split("&")) {
const c = tok.indexOf(":");
if (c < 0) continue;
const k = tok.slice(0, c).trim();
const v = tok.slice(c + 1);
if (k === "p") shot.power = +v;
else if (k === "d" || k === "x" || k === "y") (shot as any)[k] = +v;
else if (k === "s") shot.spin = Math.trunc(+v);
}
const fmt = { segs: [] as Seg[] };
const ballsBySeg: Ball[][] = [];
for (const seg of segs) {
const f = splitSeg(seg);
fmt.segs.push(f);
ballsBySeg.push(f.records.map(ball));
}
let stripes = 0;
if (replay.includes("stripes:")) {
stripes = Math.trunc(+replay.split("stripes:")[1].split("&")[0].split("|")[0]);
}
const withBalls = ballsBySeg.filter((b) => b.length);
return {
shot, segments: ballsBySeg,
initial: withBalls[0] ?? [], final: withBalls[withBalls.length - 1] ?? [],
stripes, _fmt: fmt,
};
}
export function build(state: any, _env: Record<string, string>): string {
const fmt: Seg[] | undefined = state._fmt?.segs;
if (fmt) {
const segments: Ball[][] = state.segments ?? [];
return fmt.map((f, i) => joinSeg(f, (segments[i] ?? []).map(ballStr))).join("|");
}
const s = state.shot;
const head = `&d:${f6(s.d)}&x:${f6(s.x)}&y:${f6(s.y)}&p:${f6(s.power)}&s:${Math.trunc(s.spin)}&`;
const seg0 = joinSeg({ head, has_balls: true, lead: true, records: [], trail: false, rest: "" },
(state.initial ?? []).map(ballStr));
const seg1 = joinSeg({ head: "", has_balls: true, lead: false, records: [], trail: true, rest: `&stripes:${state.stripes ?? 0}` },
(state.final ?? []).map(ballStr));
return seg0 + "|" + seg1;
}
register({ name: "pool", tokens: ["pool", "pool2", "pool3"], players: 2, parse, build, doc: "8-Ball / Pool" });