2d-mini-tennis 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/TennisGame.d.ts +2 -0
- package/dist/TennisGame.d.ts.map +1 -0
- package/dist/TennisGame.js +607 -0
- package/dist/TennisGame.js.map +1 -0
- package/dist/bot.d.ts +4 -0
- package/dist/bot.d.ts.map +1 -0
- package/dist/bot.js +85 -0
- package/dist/bot.js.map +1 -0
- package/dist/constants.d.ts +49 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +55 -0
- package/dist/constants.js.map +1 -0
- package/dist/court.d.ts +4 -0
- package/dist/court.d.ts.map +1 -0
- package/dist/court.js +63 -0
- package/dist/court.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/input.d.ts +12 -0
- package/dist/input.d.ts.map +1 -0
- package/dist/input.js +79 -0
- package/dist/input.js.map +1 -0
- package/dist/physics.d.ts +11 -0
- package/dist/physics.d.ts.map +1 -0
- package/dist/physics.js +86 -0
- package/dist/physics.js.map +1 -0
- package/dist/scoring.d.ts +10 -0
- package/dist/scoring.d.ts.map +1 -0
- package/dist/scoring.js +88 -0
- package/dist/scoring.js.map +1 -0
- package/dist/serve.d.ts +11 -0
- package/dist/serve.d.ts.map +1 -0
- package/dist/serve.js +76 -0
- package/dist/serve.js.map +1 -0
- package/dist/types.d.ts +69 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +36 -0
package/dist/bot.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { PHYSICS } from "./constants";
|
|
2
|
+
import { calcVelocity, calcArcVz, distance } from "./physics";
|
|
3
|
+
// ── Bot movement AI ──────────────────────────────────────
|
|
4
|
+
export function updateBot(state) {
|
|
5
|
+
const bot = state.bot;
|
|
6
|
+
const ball = state.ball;
|
|
7
|
+
const court = state.court;
|
|
8
|
+
// Default idle position: center of far half
|
|
9
|
+
let target = {
|
|
10
|
+
x: court.centerServiceX,
|
|
11
|
+
y: court.y + court.height * 0.2,
|
|
12
|
+
};
|
|
13
|
+
if (ball.isInPlay) {
|
|
14
|
+
// Chase ball when it's headed toward the bot's side
|
|
15
|
+
if (ball.vel.y < 0) {
|
|
16
|
+
// Predict where ball will be
|
|
17
|
+
const timeToReach = Math.abs(bot.pos.y - ball.pos.y) / Math.abs(ball.vel.y || 1);
|
|
18
|
+
target = {
|
|
19
|
+
x: ball.pos.x + ball.vel.x * timeToReach * 0.6,
|
|
20
|
+
y: ball.pos.y + ball.vel.y * timeToReach * 0.4,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
// Ball going away — return to ready position
|
|
25
|
+
target = {
|
|
26
|
+
x: court.centerServiceX,
|
|
27
|
+
y: court.y + court.height * 0.22,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// Clamp target within bot's playable area
|
|
32
|
+
target.x = Math.max(court.singlesLeft, Math.min(court.singlesRight, target.x));
|
|
33
|
+
target.y = Math.max(court.y + 10, Math.min(court.netY - 15, target.y));
|
|
34
|
+
// Move toward target at bot speed
|
|
35
|
+
const dx = target.x - bot.pos.x;
|
|
36
|
+
const dy = target.y - bot.pos.y;
|
|
37
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
38
|
+
if (dist > 1) {
|
|
39
|
+
const speed = Math.min(PHYSICS.BOT_SPEED, dist);
|
|
40
|
+
bot.pos.x += (dx / dist) * speed;
|
|
41
|
+
bot.pos.y += (dy / dist) * speed;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// ── Bot hit decision ─────────────────────────────────────
|
|
45
|
+
export function botTryHit(state) {
|
|
46
|
+
const bot = state.bot;
|
|
47
|
+
const ball = state.ball;
|
|
48
|
+
const court = state.court;
|
|
49
|
+
if (!ball.isInPlay)
|
|
50
|
+
return false;
|
|
51
|
+
if (ball.lastHitBy === "bot")
|
|
52
|
+
return false;
|
|
53
|
+
// Ball must be on bot's side and near ground (bounced)
|
|
54
|
+
if (ball.pos.y > court.netY)
|
|
55
|
+
return false;
|
|
56
|
+
if (ball.z > 8)
|
|
57
|
+
return false;
|
|
58
|
+
if (!ball.hasBounced)
|
|
59
|
+
return false;
|
|
60
|
+
const dist = distance(bot.pos, ball.pos);
|
|
61
|
+
if (dist > PHYSICS.HIT_RADIUS)
|
|
62
|
+
return false;
|
|
63
|
+
// Pick a target on the player's side
|
|
64
|
+
const target = botPickRallyTarget(state);
|
|
65
|
+
ball.vel = calcVelocity(ball.pos, target, PHYSICS.BALL_SPEED_RALLY);
|
|
66
|
+
ball.vz = calcArcVz(ball.z, ball.pos, target, PHYSICS.BALL_SPEED_RALLY);
|
|
67
|
+
ball.lastHitBy = "bot";
|
|
68
|
+
ball.hasBounced = false;
|
|
69
|
+
state.bot.swingTimer = PHYSICS.SWING_DURATION;
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
// ── Bot rally target ─────────────────────────────────────
|
|
73
|
+
function botPickRallyTarget(state) {
|
|
74
|
+
const court = state.court;
|
|
75
|
+
const player = state.player;
|
|
76
|
+
// Aim away from the player with some randomness
|
|
77
|
+
const courtMidX = court.centerServiceX;
|
|
78
|
+
const aimX = player.pos.x > courtMidX
|
|
79
|
+
? court.singlesLeft + 20 + Math.random() * 60
|
|
80
|
+
: court.singlesRight - 20 - Math.random() * 60;
|
|
81
|
+
const aimY = court.serviceLineNear +
|
|
82
|
+
Math.random() * (court.y + court.height - court.serviceLineNear - 20);
|
|
83
|
+
return { x: aimX, y: aimY };
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=bot.js.map
|
package/dist/bot.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bot.js","sourceRoot":"","sources":["../src/bot.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export declare const CANVAS_WIDTH = 480;
|
|
2
|
+
export declare const CANVAS_HEIGHT = 720;
|
|
3
|
+
export declare const COURT: {
|
|
4
|
+
MARGIN_X: number;
|
|
5
|
+
MARGIN_TOP: number;
|
|
6
|
+
MARGIN_BOTTOM: number;
|
|
7
|
+
WIDTH: number;
|
|
8
|
+
HEIGHT: number;
|
|
9
|
+
ALLEY_RATIO: number;
|
|
10
|
+
SERVICE_LINE_RATIO: number;
|
|
11
|
+
SURFACE_COLOR: string;
|
|
12
|
+
LINE_COLOR: string;
|
|
13
|
+
LINE_WIDTH: number;
|
|
14
|
+
NET_COLOR: string;
|
|
15
|
+
NET_WIDTH: number;
|
|
16
|
+
OUT_COLOR: string;
|
|
17
|
+
};
|
|
18
|
+
export declare const BALL: {
|
|
19
|
+
RADIUS: number;
|
|
20
|
+
COLOR: string;
|
|
21
|
+
STROKE_COLOR: string;
|
|
22
|
+
SHADOW_COLOR: string;
|
|
23
|
+
};
|
|
24
|
+
export declare const PLAYER: {
|
|
25
|
+
RADIUS: number;
|
|
26
|
+
COLOR: string;
|
|
27
|
+
BOT_COLOR: string;
|
|
28
|
+
STROKE_COLOR: string;
|
|
29
|
+
};
|
|
30
|
+
export declare const PHYSICS: {
|
|
31
|
+
BALL_SPEED_SERVE: number;
|
|
32
|
+
BALL_SPEED_RALLY: number;
|
|
33
|
+
BALL_GRAVITY: number;
|
|
34
|
+
TOSS_GRAVITY: number;
|
|
35
|
+
TOSS_VELOCITY: number;
|
|
36
|
+
TOSS_PEAK_Z: number;
|
|
37
|
+
BOUNCE_VZ: number;
|
|
38
|
+
BOUNCE_FRICTION: number;
|
|
39
|
+
PLAYER_LERP: number;
|
|
40
|
+
BOT_SPEED: number;
|
|
41
|
+
HIT_RADIUS: number;
|
|
42
|
+
POINT_PAUSE_FRAMES: number;
|
|
43
|
+
BOT_SERVE_DELAY: number;
|
|
44
|
+
FAULT_PAUSE_FRAMES: number;
|
|
45
|
+
SERVE_AIM_SPEED_X: number;
|
|
46
|
+
SERVE_AIM_SPEED_Y: number;
|
|
47
|
+
SWING_DURATION: number;
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY,MAAM,CAAC;AAChC,eAAO,MAAM,aAAa,MAAM,CAAC;AAIjC,eAAO,MAAM,KAAK;;;;;;;;;;;;;;CAgBjB,CAAC;AAIF,eAAO,MAAM,IAAI;;;;;CAKhB,CAAC;AAIF,eAAO,MAAM,MAAM;;;;;CAKlB,CAAC;AAIF,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;CAkBnB,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// ── Canvas ──────────────────────────────────────────────
|
|
2
|
+
export const CANVAS_WIDTH = 480;
|
|
3
|
+
export const CANVAS_HEIGHT = 720;
|
|
4
|
+
// ── Court ───────────────────────────────────────────────
|
|
5
|
+
export const COURT = {
|
|
6
|
+
MARGIN_X: 72,
|
|
7
|
+
MARGIN_TOP: 120,
|
|
8
|
+
MARGIN_BOTTOM: 120,
|
|
9
|
+
WIDTH: 336, // 280 * 1.2
|
|
10
|
+
HEIGHT: 480, // 600 * 0.8
|
|
11
|
+
ALLEY_RATIO: 0.125, // doubles alley: 4.5ft / 36ft per side
|
|
12
|
+
SERVICE_LINE_RATIO: 0.54, // service line 21ft from net / 39ft half-court
|
|
13
|
+
// Colors
|
|
14
|
+
SURFACE_COLOR: "#2D6B3F",
|
|
15
|
+
LINE_COLOR: "#FFFFFF",
|
|
16
|
+
LINE_WIDTH: 2,
|
|
17
|
+
NET_COLOR: "#CCCCCC",
|
|
18
|
+
NET_WIDTH: 3,
|
|
19
|
+
OUT_COLOR: "#1A4028",
|
|
20
|
+
};
|
|
21
|
+
// ── Ball ────────────────────────────────────────────────
|
|
22
|
+
export const BALL = {
|
|
23
|
+
RADIUS: 5,
|
|
24
|
+
COLOR: "#CCFF00",
|
|
25
|
+
STROKE_COLOR: "#99AA00",
|
|
26
|
+
SHADOW_COLOR: "rgba(0,0,0,0.25)",
|
|
27
|
+
};
|
|
28
|
+
// ── Players ─────────────────────────────────────────────
|
|
29
|
+
export const PLAYER = {
|
|
30
|
+
RADIUS: 12,
|
|
31
|
+
COLOR: "#4488FF",
|
|
32
|
+
BOT_COLOR: "#FF4444",
|
|
33
|
+
STROKE_COLOR: "rgba(0,0,0,0.3)",
|
|
34
|
+
};
|
|
35
|
+
// ── Physics ─────────────────────────────────────────────
|
|
36
|
+
export const PHYSICS = {
|
|
37
|
+
BALL_SPEED_SERVE: 5,
|
|
38
|
+
BALL_SPEED_RALLY: 4,
|
|
39
|
+
BALL_GRAVITY: 0.05, // gentle arc during flight
|
|
40
|
+
TOSS_GRAVITY: 0.25, // fast toss animation
|
|
41
|
+
TOSS_VELOCITY: 5,
|
|
42
|
+
TOSS_PEAK_Z: 50,
|
|
43
|
+
BOUNCE_VZ: 0.8, // small upward bounce after landing
|
|
44
|
+
BOUNCE_FRICTION: 0.6, // horizontal slowdown on bounce
|
|
45
|
+
PLAYER_LERP: 0.08, // mouse-follow smoothing
|
|
46
|
+
BOT_SPEED: 2.2,
|
|
47
|
+
HIT_RADIUS: 35,
|
|
48
|
+
POINT_PAUSE_FRAMES: 90, // ~1.5s at 60fps
|
|
49
|
+
BOT_SERVE_DELAY: 60, // ~1s before bot auto-serves
|
|
50
|
+
FAULT_PAUSE_FRAMES: 60, // ~1s fault message display
|
|
51
|
+
SERVE_AIM_SPEED_X: 0.022, // horizontal sweep speed (radians/frame)
|
|
52
|
+
SERVE_AIM_SPEED_Y: 0.015, // vertical sweep speed (radians/frame)
|
|
53
|
+
SWING_DURATION: 12, // racket swing animation frames (~200ms at 60fps)
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAE3D,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,CAAC;AAChC,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,CAAC;AAEjC,2DAA2D;AAE3D,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,QAAQ,EAAE,EAAE;IACZ,UAAU,EAAE,GAAG;IACf,aAAa,EAAE,GAAG;IAClB,KAAK,EAAE,GAAG,EAAE,YAAY;IACxB,MAAM,EAAE,GAAG,EAAE,YAAY;IACzB,WAAW,EAAE,KAAK,EAAE,uCAAuC;IAC3D,kBAAkB,EAAE,IAAI,EAAE,+CAA+C;IAEzE,SAAS;IACT,aAAa,EAAE,SAAS;IACxB,UAAU,EAAE,SAAS;IACrB,UAAU,EAAE,CAAC;IACb,SAAS,EAAE,SAAS;IACpB,SAAS,EAAE,CAAC;IACZ,SAAS,EAAE,SAAS;CACrB,CAAC;AAEF,2DAA2D;AAE3D,MAAM,CAAC,MAAM,IAAI,GAAG;IAClB,MAAM,EAAE,CAAC;IACT,KAAK,EAAE,SAAS;IAChB,YAAY,EAAE,SAAS;IACvB,YAAY,EAAE,kBAAkB;CACjC,CAAC;AAEF,2DAA2D;AAE3D,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,MAAM,EAAE,EAAE;IACV,KAAK,EAAE,SAAS;IAChB,SAAS,EAAE,SAAS;IACpB,YAAY,EAAE,iBAAiB;CAChC,CAAC;AAEF,2DAA2D;AAE3D,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,gBAAgB,EAAE,CAAC;IACnB,gBAAgB,EAAE,CAAC;IACnB,YAAY,EAAE,IAAI,EAAE,2BAA2B;IAC/C,YAAY,EAAE,IAAI,EAAE,sBAAsB;IAC1C,aAAa,EAAE,CAAC;IAChB,WAAW,EAAE,EAAE;IACf,SAAS,EAAE,GAAG,EAAE,oCAAoC;IACpD,eAAe,EAAE,GAAG,EAAE,gCAAgC;IACtD,WAAW,EAAE,IAAI,EAAE,yBAAyB;IAC5C,SAAS,EAAE,GAAG;IACd,UAAU,EAAE,EAAE;IACd,kBAAkB,EAAE,EAAE,EAAE,iBAAiB;IACzC,eAAe,EAAE,EAAE,EAAE,6BAA6B;IAClD,kBAAkB,EAAE,EAAE,EAAE,4BAA4B;IACpD,iBAAiB,EAAE,KAAK,EAAE,yCAAyC;IACnE,iBAAiB,EAAE,KAAK,EAAE,uCAAuC;IACjE,cAAc,EAAE,EAAE,EAAE,kDAAkD;CACvE,CAAC"}
|
package/dist/court.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"court.d.ts","sourceRoot":"","sources":["../src/court.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,wBAAgB,qBAAqB,IAAI,eAAe,CAsBvD;AAeD,wBAAgB,SAAS,CACvB,GAAG,EAAE,wBAAwB,EAC7B,KAAK,EAAE,eAAe,QA0CvB"}
|
package/dist/court.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { CANVAS_WIDTH, CANVAS_HEIGHT, COURT } from "./constants";
|
|
2
|
+
export function createCourtDimensions() {
|
|
3
|
+
const x = COURT.MARGIN_X;
|
|
4
|
+
const y = COURT.MARGIN_TOP;
|
|
5
|
+
const width = COURT.WIDTH;
|
|
6
|
+
const height = COURT.HEIGHT;
|
|
7
|
+
const netY = y + height / 2;
|
|
8
|
+
const halfH = height / 2;
|
|
9
|
+
const serviceOffset = halfH * COURT.SERVICE_LINE_RATIO;
|
|
10
|
+
const alleyW = width * COURT.ALLEY_RATIO;
|
|
11
|
+
return {
|
|
12
|
+
x,
|
|
13
|
+
y,
|
|
14
|
+
width,
|
|
15
|
+
height,
|
|
16
|
+
netY,
|
|
17
|
+
serviceLineNear: netY + serviceOffset,
|
|
18
|
+
serviceLineFar: netY - serviceOffset,
|
|
19
|
+
centerServiceX: x + width / 2,
|
|
20
|
+
singlesLeft: x + alleyW,
|
|
21
|
+
singlesRight: x + width - alleyW,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function line(ctx, x1, y1, x2, y2) {
|
|
25
|
+
ctx.beginPath();
|
|
26
|
+
ctx.moveTo(x1, y1);
|
|
27
|
+
ctx.lineTo(x2, y2);
|
|
28
|
+
ctx.stroke();
|
|
29
|
+
}
|
|
30
|
+
export function drawCourt(ctx, court) {
|
|
31
|
+
// Out-of-bounds background
|
|
32
|
+
ctx.fillStyle = COURT.OUT_COLOR;
|
|
33
|
+
ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
|
|
34
|
+
// Court surface
|
|
35
|
+
ctx.fillStyle = COURT.SURFACE_COLOR;
|
|
36
|
+
ctx.fillRect(court.x, court.y, court.width, court.height);
|
|
37
|
+
// White lines
|
|
38
|
+
ctx.strokeStyle = COURT.LINE_COLOR;
|
|
39
|
+
ctx.lineWidth = COURT.LINE_WIDTH;
|
|
40
|
+
// Outer boundary
|
|
41
|
+
ctx.strokeRect(court.x, court.y, court.width, court.height);
|
|
42
|
+
// Singles sidelines
|
|
43
|
+
line(ctx, court.singlesLeft, court.y, court.singlesLeft, court.y + court.height);
|
|
44
|
+
line(ctx, court.singlesRight, court.y, court.singlesRight, court.y + court.height);
|
|
45
|
+
// Service lines
|
|
46
|
+
line(ctx, court.singlesLeft, court.serviceLineFar, court.singlesRight, court.serviceLineFar);
|
|
47
|
+
line(ctx, court.singlesLeft, court.serviceLineNear, court.singlesRight, court.serviceLineNear);
|
|
48
|
+
// Center service line
|
|
49
|
+
line(ctx, court.centerServiceX, court.serviceLineFar, court.centerServiceX, court.serviceLineNear);
|
|
50
|
+
// Center ticks on baselines
|
|
51
|
+
const tick = 8;
|
|
52
|
+
line(ctx, court.centerServiceX, court.y, court.centerServiceX, court.y + tick);
|
|
53
|
+
line(ctx, court.centerServiceX, court.y + court.height, court.centerServiceX, court.y + court.height - tick);
|
|
54
|
+
// Net
|
|
55
|
+
ctx.strokeStyle = COURT.NET_COLOR;
|
|
56
|
+
ctx.lineWidth = COURT.NET_WIDTH;
|
|
57
|
+
line(ctx, court.x - 10, court.netY, court.x + court.width + 10, court.netY);
|
|
58
|
+
// Net posts
|
|
59
|
+
ctx.fillStyle = COURT.NET_COLOR;
|
|
60
|
+
ctx.fillRect(court.x - 14, court.netY - 3, 6, 6);
|
|
61
|
+
ctx.fillRect(court.x + court.width + 8, court.netY - 3, 6, 6);
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=court.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"court.js","sourceRoot":"","sources":["../src/court.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { default as TennisGame } from "./TennisGame";
|
|
2
|
+
export type { Vec2, Ball, Player, Score, ServePhase, ServeState, GamePhase, CourtDimensions, GameState, } from "./types";
|
|
3
|
+
export { CANVAS_WIDTH, CANVAS_HEIGHT, COURT, BALL, PLAYER, PHYSICS } from "./constants";
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,cAAc,CAAC;AACrD,YAAY,EACV,IAAI,EACJ,IAAI,EACJ,MAAM,EACN,KAAK,EACL,UAAU,EACV,UAAU,EACV,SAAS,EACT,eAAe,EACf,SAAS,GACV,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,cAAc,CAAC;AAYrD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/input.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Vec2 } from "./types";
|
|
2
|
+
export interface InputState {
|
|
3
|
+
mousePos: Vec2;
|
|
4
|
+
mouseClicked: boolean;
|
|
5
|
+
mouseDown: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function createInputHandlers(canvas: HTMLCanvasElement): {
|
|
8
|
+
state: InputState;
|
|
9
|
+
consumeClick(): void;
|
|
10
|
+
cleanup(): void;
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=input.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../src/input.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAEpC,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,IAAI,CAAC;IACf,YAAY,EAAE,OAAO,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,iBAAiB;;;;EAmF5D"}
|
package/dist/input.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export function createInputHandlers(canvas) {
|
|
2
|
+
const state = {
|
|
3
|
+
mousePos: { x: 0, y: 0 },
|
|
4
|
+
mouseClicked: false,
|
|
5
|
+
mouseDown: false,
|
|
6
|
+
};
|
|
7
|
+
function toCanvasMouse(e) {
|
|
8
|
+
const rect = canvas.getBoundingClientRect();
|
|
9
|
+
const scaleX = canvas.width / rect.width;
|
|
10
|
+
const scaleY = canvas.height / rect.height;
|
|
11
|
+
return {
|
|
12
|
+
x: (e.clientX - rect.left) * scaleX,
|
|
13
|
+
y: (e.clientY - rect.top) * scaleY,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function toCanvasTouch(t) {
|
|
17
|
+
const rect = canvas.getBoundingClientRect();
|
|
18
|
+
const scaleX = canvas.width / rect.width;
|
|
19
|
+
const scaleY = canvas.height / rect.height;
|
|
20
|
+
return {
|
|
21
|
+
x: (t.clientX - rect.left) * scaleX,
|
|
22
|
+
y: (t.clientY - rect.top) * scaleY,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
// Mouse events
|
|
26
|
+
const onMove = (e) => {
|
|
27
|
+
state.mousePos = toCanvasMouse(e);
|
|
28
|
+
};
|
|
29
|
+
const onDown = (e) => {
|
|
30
|
+
state.mousePos = toCanvasMouse(e);
|
|
31
|
+
state.mouseDown = true;
|
|
32
|
+
state.mouseClicked = true;
|
|
33
|
+
};
|
|
34
|
+
const onUp = () => {
|
|
35
|
+
state.mouseDown = false;
|
|
36
|
+
};
|
|
37
|
+
// Touch events
|
|
38
|
+
const onTouchStart = (e) => {
|
|
39
|
+
e.preventDefault();
|
|
40
|
+
const touch = e.touches[0];
|
|
41
|
+
if (touch) {
|
|
42
|
+
state.mousePos = toCanvasTouch(touch);
|
|
43
|
+
state.mouseDown = true;
|
|
44
|
+
state.mouseClicked = true;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
const onTouchMove = (e) => {
|
|
48
|
+
e.preventDefault();
|
|
49
|
+
const touch = e.touches[0];
|
|
50
|
+
if (touch) {
|
|
51
|
+
state.mousePos = toCanvasTouch(touch);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const onTouchEnd = (e) => {
|
|
55
|
+
e.preventDefault();
|
|
56
|
+
state.mouseDown = false;
|
|
57
|
+
};
|
|
58
|
+
canvas.addEventListener("mousemove", onMove);
|
|
59
|
+
canvas.addEventListener("mousedown", onDown);
|
|
60
|
+
canvas.addEventListener("mouseup", onUp);
|
|
61
|
+
canvas.addEventListener("touchstart", onTouchStart, { passive: false });
|
|
62
|
+
canvas.addEventListener("touchmove", onTouchMove, { passive: false });
|
|
63
|
+
canvas.addEventListener("touchend", onTouchEnd, { passive: false });
|
|
64
|
+
return {
|
|
65
|
+
state,
|
|
66
|
+
consumeClick() {
|
|
67
|
+
state.mouseClicked = false;
|
|
68
|
+
},
|
|
69
|
+
cleanup() {
|
|
70
|
+
canvas.removeEventListener("mousemove", onMove);
|
|
71
|
+
canvas.removeEventListener("mousedown", onDown);
|
|
72
|
+
canvas.removeEventListener("mouseup", onUp);
|
|
73
|
+
canvas.removeEventListener("touchstart", onTouchStart);
|
|
74
|
+
canvas.removeEventListener("touchmove", onTouchMove);
|
|
75
|
+
canvas.removeEventListener("touchend", onTouchEnd);
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input.js","sourceRoot":"","sources":["../src/input.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Ball, Vec2, CourtDimensions } from "./types";
|
|
2
|
+
export declare function updateBall(ball: Ball): void;
|
|
3
|
+
export declare function calcArcVz(startZ: number, from: Vec2, to: Vec2, speed: number): number;
|
|
4
|
+
export declare function calcVelocity(from: Vec2, to: Vec2, speed: number): Vec2;
|
|
5
|
+
export declare function distance(a: Vec2, b: Vec2): number;
|
|
6
|
+
export declare function magnitude(v: Vec2): number;
|
|
7
|
+
export declare function isInServiceBox(pos: Vec2, court: CourtDimensions, targetSide: "near" | "far", targetBox: "left" | "right"): boolean;
|
|
8
|
+
export declare function isOnSide(pos: Vec2, court: CourtDimensions, side: "near" | "far"): boolean;
|
|
9
|
+
export declare function ballOutOfCourt(ball: Ball, court: CourtDimensions): boolean;
|
|
10
|
+
export declare function isInSingles(pos: Vec2, court: CourtDimensions): boolean;
|
|
11
|
+
//# sourceMappingURL=physics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"physics.d.ts","sourceRoot":"","sources":["../src/physics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAK3D,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAe3C;AAID,wBAAgB,SAAS,CACvB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,IAAI,EACV,EAAE,EAAE,IAAI,EACR,KAAK,EAAE,MAAM,GACZ,MAAM,CAOR;AAID,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAMtE;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,GAAG,MAAM,CAIjD;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,IAAI,GAAG,MAAM,CAEzC;AAID,wBAAgB,cAAc,CAC5B,GAAG,EAAE,IAAI,EACT,KAAK,EAAE,eAAe,EACtB,UAAU,EAAE,MAAM,GAAG,KAAK,EAC1B,SAAS,EAAE,MAAM,GAAG,OAAO,GAC1B,OAAO,CA0BT;AAED,wBAAgB,QAAQ,CACtB,GAAG,EAAE,IAAI,EACT,KAAK,EAAE,eAAe,EACtB,IAAI,EAAE,MAAM,GAAG,KAAK,GACnB,OAAO,CAKT;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAO1E;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAOtE"}
|
package/dist/physics.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { PHYSICS } from "./constants";
|
|
2
|
+
// ── Ball movement ───────────────────────────────────────
|
|
3
|
+
export function updateBall(ball) {
|
|
4
|
+
ball.pos.x += ball.vel.x;
|
|
5
|
+
ball.pos.y += ball.vel.y;
|
|
6
|
+
ball.z += ball.vz;
|
|
7
|
+
ball.vz -= PHYSICS.BALL_GRAVITY;
|
|
8
|
+
// For non-in-play balls (toss idle, etc), hard stop at ground
|
|
9
|
+
if (ball.z < 0 && !ball.isInPlay) {
|
|
10
|
+
ball.z = 0;
|
|
11
|
+
ball.vz = 0;
|
|
12
|
+
}
|
|
13
|
+
ball.shadow.x = ball.pos.x;
|
|
14
|
+
ball.shadow.y = ball.pos.y;
|
|
15
|
+
}
|
|
16
|
+
// ── Calculate arc vz so ball lands at target ────────────
|
|
17
|
+
export function calcArcVz(startZ, from, to, speed) {
|
|
18
|
+
const dist = distance(from, to);
|
|
19
|
+
const frames = dist / Math.max(speed, 0.1);
|
|
20
|
+
if (frames <= 1)
|
|
21
|
+
return 0;
|
|
22
|
+
// z(T) = startZ + vz*T - 0.5*g*T² = 0
|
|
23
|
+
// vz = (0.5*g*T² - startZ) / T
|
|
24
|
+
return (0.5 * PHYSICS.BALL_GRAVITY * frames * frames - startZ) / frames;
|
|
25
|
+
}
|
|
26
|
+
// ── Velocity helpers ────────────────────────────────────
|
|
27
|
+
export function calcVelocity(from, to, speed) {
|
|
28
|
+
const dx = to.x - from.x;
|
|
29
|
+
const dy = to.y - from.y;
|
|
30
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
31
|
+
if (dist === 0)
|
|
32
|
+
return { x: 0, y: -speed };
|
|
33
|
+
return { x: (dx / dist) * speed, y: (dy / dist) * speed };
|
|
34
|
+
}
|
|
35
|
+
export function distance(a, b) {
|
|
36
|
+
const dx = a.x - b.x;
|
|
37
|
+
const dy = a.y - b.y;
|
|
38
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
39
|
+
}
|
|
40
|
+
export function magnitude(v) {
|
|
41
|
+
return Math.sqrt(v.x * v.x + v.y * v.y);
|
|
42
|
+
}
|
|
43
|
+
// ── Bounds checking ─────────────────────────────────────
|
|
44
|
+
export function isInServiceBox(pos, court, targetSide, targetBox) {
|
|
45
|
+
const mid = court.centerServiceX;
|
|
46
|
+
let boxLeft, boxRight, boxTop, boxBottom;
|
|
47
|
+
if (targetSide === "far") {
|
|
48
|
+
boxTop = court.serviceLineFar;
|
|
49
|
+
boxBottom = court.netY;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
boxTop = court.netY;
|
|
53
|
+
boxBottom = court.serviceLineNear;
|
|
54
|
+
}
|
|
55
|
+
if (targetBox === "left") {
|
|
56
|
+
boxLeft = court.singlesLeft;
|
|
57
|
+
boxRight = mid;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
boxLeft = mid;
|
|
61
|
+
boxRight = court.singlesRight;
|
|
62
|
+
}
|
|
63
|
+
return (pos.x >= boxLeft &&
|
|
64
|
+
pos.x <= boxRight &&
|
|
65
|
+
pos.y >= boxTop &&
|
|
66
|
+
pos.y <= boxBottom);
|
|
67
|
+
}
|
|
68
|
+
export function isOnSide(pos, court, side) {
|
|
69
|
+
if (side === "near") {
|
|
70
|
+
return pos.y > court.netY;
|
|
71
|
+
}
|
|
72
|
+
return pos.y < court.netY;
|
|
73
|
+
}
|
|
74
|
+
export function ballOutOfCourt(ball, court) {
|
|
75
|
+
return (ball.pos.y < court.y - 60 ||
|
|
76
|
+
ball.pos.y > court.y + court.height + 60 ||
|
|
77
|
+
ball.pos.x < court.x - 60 ||
|
|
78
|
+
ball.pos.x > court.x + court.width + 60);
|
|
79
|
+
}
|
|
80
|
+
export function isInSingles(pos, court) {
|
|
81
|
+
return (pos.x >= court.singlesLeft &&
|
|
82
|
+
pos.x <= court.singlesRight &&
|
|
83
|
+
pos.y >= court.y &&
|
|
84
|
+
pos.y <= court.y + court.height);
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=physics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"physics.js","sourceRoot":"","sources":["../src/physics.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Score } from "./types";
|
|
2
|
+
export declare function createInitialScore(): Score;
|
|
3
|
+
export declare function awardPoint(score: Score, winner: "player" | "bot"): {
|
|
4
|
+
gameOver: boolean;
|
|
5
|
+
setOver: boolean;
|
|
6
|
+
};
|
|
7
|
+
export declare function toggleServiceBox(score: Score): void;
|
|
8
|
+
export declare function formatPointScore(score: Score): string;
|
|
9
|
+
export declare function formatGameScore(score: Score): string;
|
|
10
|
+
//# sourceMappingURL=scoring.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scoring.d.ts","sourceRoot":"","sources":["../src/scoring.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAIrC,wBAAgB,kBAAkB,IAAI,KAAK,CAY1C;AAID,wBAAgB,UAAU,CACxB,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,QAAQ,GAAG,KAAK,GACvB;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAsCzC;AAgCD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAEnD;AAID,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAMrD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAEpD"}
|
package/dist/scoring.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// ── Create initial score ─────────────────────────────────
|
|
2
|
+
export function createInitialScore() {
|
|
3
|
+
return {
|
|
4
|
+
player: 0,
|
|
5
|
+
bot: 0,
|
|
6
|
+
playerGames: 0,
|
|
7
|
+
botGames: 0,
|
|
8
|
+
isDeuce: false,
|
|
9
|
+
advantage: null,
|
|
10
|
+
servingSide: "player",
|
|
11
|
+
serviceBox: "right",
|
|
12
|
+
serveAttempt: 1,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
// ── Award a point ────────────────────────────────────────
|
|
16
|
+
export function awardPoint(score, winner) {
|
|
17
|
+
const loser = winner === "player" ? "bot" : "player";
|
|
18
|
+
// Handle deuce/advantage
|
|
19
|
+
if (score.isDeuce) {
|
|
20
|
+
if (score.advantage === winner) {
|
|
21
|
+
// Advantage + point = game won
|
|
22
|
+
return finishGame(score, winner);
|
|
23
|
+
}
|
|
24
|
+
else if (score.advantage === loser) {
|
|
25
|
+
// Back to deuce
|
|
26
|
+
score.advantage = null;
|
|
27
|
+
return { gameOver: false, setOver: false };
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
// No advantage — give advantage to winner
|
|
31
|
+
score.advantage = winner;
|
|
32
|
+
return { gameOver: false, setOver: false };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Normal scoring: 0 → 15 → 30 → 40
|
|
36
|
+
const points = [0, 15, 30, 40];
|
|
37
|
+
const current = score[winner];
|
|
38
|
+
const idx = points.indexOf(current);
|
|
39
|
+
if (current === 40) {
|
|
40
|
+
if (score[loser] === 40) {
|
|
41
|
+
// Enter deuce
|
|
42
|
+
score.isDeuce = true;
|
|
43
|
+
score.advantage = winner;
|
|
44
|
+
return { gameOver: false, setOver: false };
|
|
45
|
+
}
|
|
46
|
+
// Win the game
|
|
47
|
+
return finishGame(score, winner);
|
|
48
|
+
}
|
|
49
|
+
// Advance score
|
|
50
|
+
score[winner] = points[idx + 1];
|
|
51
|
+
return { gameOver: false, setOver: false };
|
|
52
|
+
}
|
|
53
|
+
// ── Finish a game ────────────────────────────────────────
|
|
54
|
+
function finishGame(score, winner) {
|
|
55
|
+
const gamesKey = winner === "player" ? "playerGames" : "botGames";
|
|
56
|
+
score[gamesKey] += 1;
|
|
57
|
+
// Check for set win (first to 6)
|
|
58
|
+
if (score[gamesKey] >= 6) {
|
|
59
|
+
return { gameOver: true, setOver: true };
|
|
60
|
+
}
|
|
61
|
+
// Reset point score
|
|
62
|
+
score.player = 0;
|
|
63
|
+
score.bot = 0;
|
|
64
|
+
score.isDeuce = false;
|
|
65
|
+
score.advantage = null;
|
|
66
|
+
// Switch server
|
|
67
|
+
score.servingSide = score.servingSide === "player" ? "bot" : "player";
|
|
68
|
+
score.serviceBox = "right"; // always start from right
|
|
69
|
+
score.serveAttempt = 1;
|
|
70
|
+
return { gameOver: false, setOver: false };
|
|
71
|
+
}
|
|
72
|
+
// ── Toggle service box ───────────────────────────────────
|
|
73
|
+
export function toggleServiceBox(score) {
|
|
74
|
+
score.serviceBox = score.serviceBox === "right" ? "left" : "right";
|
|
75
|
+
}
|
|
76
|
+
// ── Format score for display ─────────────────────────────
|
|
77
|
+
export function formatPointScore(score) {
|
|
78
|
+
if (score.isDeuce) {
|
|
79
|
+
if (score.advantage === null)
|
|
80
|
+
return "Deuce";
|
|
81
|
+
return `Adv ${score.advantage === "player" ? "You" : "Bot"}`;
|
|
82
|
+
}
|
|
83
|
+
return `${score.player} - ${score.bot}`;
|
|
84
|
+
}
|
|
85
|
+
export function formatGameScore(score) {
|
|
86
|
+
return `Games: ${score.playerGames} - ${score.botGames}`;
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=scoring.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scoring.js","sourceRoot":"","sources":["../src/scoring.ts"],"names":[],"mappings":"AAEA,4DAA4D;AAE5D,MAAM,UAAU,kBAAkB;IAChC,OAAO;QACL,MAAM,EAAE,CAAC;QACT,GAAG,EAAE,CAAC;QACN,WAAW,EAAE,CAAC;QACd,QAAQ,EAAE,CAAC;QACX,OAAO,EAAE,KAAK;QACd,SAAS,EAAE,IAAI;QACf,WAAW,EAAE,QAAQ;QACrB,UAAU,EAAE,OAAO;QACnB,YAAY,EAAE,CAAC;KAChB,CAAC;AACJ,CAAC;AAED,4DAA4D;AAE5D,MAAM,UAAU,UAAU,CACxB,KAAY,EACZ,MAAwB;IAExB,MAAM,KAAK,GAAG,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC;IAErD,yBAAyB;IACzB,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QAClB,IAAI,KAAK,CAAC,SAAS,KAAK,MAAM,EAAE,CAAC;YAC/B,+BAA+B;YAC/B,OAAO,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACnC,CAAC;aAAM,IAAI,KAAK,CAAC,SAAS,KAAK,KAAK,EAAE,CAAC;YACrC,gBAAgB;YAChB,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC;YACvB,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QAC7C,CAAC;aAAM,CAAC;YACN,0CAA0C;YAC1C,KAAK,CAAC,SAAS,GAAG,MAAM,CAAC;YACzB,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QAC7C,CAAC;IACH,CAAC;IAED,mCAAmC;IACnC,MAAM,MAAM,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;IAC/B,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;IAC9B,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAEpC,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;QACnB,IAAI,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;YACxB,cAAc;YACd,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC;YACrB,KAAK,CAAC,SAAS,GAAG,MAAM,CAAC;YACzB,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QAC7C,CAAC;QACD,eAAe;QACf,OAAO,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACnC,CAAC;IAED,gBAAgB;IAChB,KAAK,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IAChC,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAC7C,CAAC;AAED,4DAA4D;AAE5D,SAAS,UAAU,CACjB,KAAY,EACZ,MAAwB;IAExB,MAAM,QAAQ,GAAG,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,UAAU,CAAC;IAClE,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAErB,iCAAiC;IACjC,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC3C,CAAC;IAED,oBAAoB;IACpB,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IACjB,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC;IACd,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC;IACtB,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC;IAEvB,gBAAgB;IAChB,KAAK,CAAC,WAAW,GAAG,KAAK,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC;IACtE,KAAK,CAAC,UAAU,GAAG,OAAO,CAAC,CAAC,0BAA0B;IACtD,KAAK,CAAC,YAAY,GAAG,CAAC,CAAC;IAEvB,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAC7C,CAAC;AAED,4DAA4D;AAE5D,MAAM,UAAU,gBAAgB,CAAC,KAAY;IAC3C,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;AACrE,CAAC;AAED,4DAA4D;AAE5D,MAAM,UAAU,gBAAgB,CAAC,KAAY;IAC3C,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QAClB,IAAI,KAAK,CAAC,SAAS,KAAK,IAAI;YAAE,OAAO,OAAO,CAAC;QAC7C,OAAO,OAAO,KAAK,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC;IAC/D,CAAC;IACD,OAAO,GAAG,KAAK,CAAC,MAAM,MAAM,KAAK,CAAC,GAAG,EAAE,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAY;IAC1C,OAAO,UAAU,KAAK,CAAC,WAAW,MAAM,KAAK,CAAC,QAAQ,EAAE,CAAC;AAC3D,CAAC"}
|