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.
@@ -0,0 +1,11 @@
1
+ import type { GameState, Vec2 } from "./types";
2
+ export declare function startToss(state: GameState): void;
3
+ export declare function updateToss(state: GameState): void;
4
+ export declare function getServiceBox(state: GameState): {
5
+ left: number;
6
+ right: number;
7
+ top: number;
8
+ bottom: number;
9
+ };
10
+ export declare function fireServe(state: GameState, target: Vec2): void;
11
+ //# sourceMappingURL=serve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../src/serve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAM/C,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAchD;AAID,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAoBjD;AAID,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,GAAG;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB,CAcA;AAID,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,GAAG,IAAI,CAmB9D"}
package/dist/serve.js ADDED
@@ -0,0 +1,76 @@
1
+ import { PHYSICS } from "./constants";
2
+ import { calcVelocity, calcArcVz } from "./physics";
3
+ // ── Start the ball toss ─────────────────────────────────
4
+ export function startToss(state) {
5
+ const server = state.score.servingSide === "player" ? state.player : state.bot;
6
+ state.phase = "SERVE_TOSS";
7
+ state.ball.pos = { ...server.pos };
8
+ state.ball.z = 0;
9
+ state.ball.vz = PHYSICS.TOSS_VELOCITY;
10
+ state.ball.vel = { x: 0, y: 0 };
11
+ state.ball.isInPlay = false;
12
+ state.ball.hasBounced = false;
13
+ state.ball.lastHitBy = null;
14
+ state.serve.phase = "tossing";
15
+ state.serve.tossProgress = 0;
16
+ }
17
+ // ── Update toss animation ───────────────────────────────
18
+ export function updateToss(state) {
19
+ state.ball.z += state.ball.vz;
20
+ state.ball.vz -= PHYSICS.TOSS_GRAVITY;
21
+ state.serve.tossProgress = Math.min(1, state.ball.z / PHYSICS.TOSS_PEAK_Z);
22
+ // Toss peaked — switch to aiming
23
+ if (state.ball.vz <= 0 && state.ball.z > 0) {
24
+ if (state.score.servingSide === "player") {
25
+ state.phase = "SERVE_AIM";
26
+ state.serve.phase = "aiming";
27
+ state.serve.aimTimer = Math.random() * 100; // random start position
28
+ }
29
+ else {
30
+ // Bot auto-aims
31
+ const target = botPickServeTarget(state);
32
+ fireServe(state, target);
33
+ }
34
+ }
35
+ }
36
+ // ── Get the service box bounds for the current serve ────
37
+ export function getServiceBox(state) {
38
+ const court = state.court;
39
+ const mid = court.centerServiceX;
40
+ const box = state.score.serviceBox;
41
+ const servingSide = state.score.servingSide;
42
+ // Server aims into opponent's service box
43
+ const isPlayerServing = servingSide === "player";
44
+ const boxTop = isPlayerServing ? court.serviceLineFar : court.netY;
45
+ const boxBottom = isPlayerServing ? court.netY : court.serviceLineNear;
46
+ const boxLeft = box === "left" ? court.singlesLeft : mid;
47
+ const boxRight = box === "left" ? mid : court.singlesRight;
48
+ return { left: boxLeft, right: boxRight, top: boxTop, bottom: boxBottom };
49
+ }
50
+ // ── Fire the serve ──────────────────────────────────────
51
+ export function fireServe(state, target) {
52
+ state.phase = "SERVE_FIRE";
53
+ state.serve.phase = "fired";
54
+ state.serve.aimTarget = target;
55
+ state.ball.vel = calcVelocity(state.ball.pos, target, PHYSICS.BALL_SPEED_SERVE);
56
+ state.ball.vz = calcArcVz(state.ball.z, state.ball.pos, target, PHYSICS.BALL_SPEED_SERVE);
57
+ state.ball.isInPlay = true;
58
+ state.ball.hasBounced = false;
59
+ state.ball.lastHitBy = state.score.servingSide;
60
+ }
61
+ // ── Bot picks a random valid serve target ───────────────
62
+ function botPickServeTarget(state) {
63
+ const court = state.court;
64
+ const mid = court.centerServiceX;
65
+ const box = state.score.serviceBox;
66
+ // Bot serves into near side (player's service box)
67
+ const boxTop = court.netY;
68
+ const boxBottom = court.serviceLineNear;
69
+ const boxLeft = box === "left" ? court.singlesLeft : mid;
70
+ const boxRight = box === "left" ? mid : court.singlesRight;
71
+ return {
72
+ x: boxLeft + 10 + Math.random() * (boxRight - boxLeft - 20),
73
+ y: boxTop + 10 + Math.random() * (boxBottom - boxTop - 20),
74
+ };
75
+ }
76
+ //# sourceMappingURL=serve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.js","sourceRoot":"","sources":["../src/serve.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ export interface Vec2 {
2
+ x: number;
3
+ y: number;
4
+ }
5
+ export interface Ball {
6
+ pos: Vec2;
7
+ vel: Vec2;
8
+ z: number;
9
+ vz: number;
10
+ radius: number;
11
+ shadow: Vec2;
12
+ isInPlay: boolean;
13
+ lastHitBy: "player" | "bot" | null;
14
+ hasBounced: boolean;
15
+ }
16
+ export interface Player {
17
+ pos: Vec2;
18
+ radius: number;
19
+ color: string;
20
+ side: "near" | "far";
21
+ swingTimer: number;
22
+ }
23
+ export interface Score {
24
+ player: number;
25
+ bot: number;
26
+ playerGames: number;
27
+ botGames: number;
28
+ isDeuce: boolean;
29
+ advantage: "player" | "bot" | null;
30
+ servingSide: "player" | "bot";
31
+ serviceBox: "left" | "right";
32
+ serveAttempt: 1 | 2;
33
+ }
34
+ export type ServePhase = "idle" | "tossing" | "aiming" | "fired";
35
+ export interface ServeState {
36
+ phase: ServePhase;
37
+ tossProgress: number;
38
+ aimTarget: Vec2 | null;
39
+ aimTimer: number;
40
+ }
41
+ export type GamePhase = "IDLE" | "SERVE_SETUP" | "SERVE_TOSS" | "SERVE_AIM" | "SERVE_FIRE" | "FAULT" | "RALLY" | "POINT_SCORED" | "GAME_OVER";
42
+ export interface CourtDimensions {
43
+ x: number;
44
+ y: number;
45
+ width: number;
46
+ height: number;
47
+ netY: number;
48
+ serviceLineNear: number;
49
+ serviceLineFar: number;
50
+ centerServiceX: number;
51
+ singlesLeft: number;
52
+ singlesRight: number;
53
+ }
54
+ export interface GameState {
55
+ phase: GamePhase;
56
+ ball: Ball;
57
+ player: Player;
58
+ bot: Player;
59
+ score: Score;
60
+ serve: ServeState;
61
+ court: CourtDimensions;
62
+ mousePos: Vec2;
63
+ lastPointWinner: "player" | "bot" | null;
64
+ pointPauseTimer: number;
65
+ botServeTimer: number;
66
+ faultMessage: string | null;
67
+ faultTimer: number;
68
+ }
69
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,IAAI;IACnB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAID,MAAM,WAAW,IAAI;IACnB,GAAG,EAAE,IAAI,CAAC;IACV,GAAG,EAAE,IAAI,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,IAAI,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,QAAQ,GAAG,KAAK,GAAG,IAAI,CAAC;IACnC,UAAU,EAAE,OAAO,CAAC;CACrB;AAID,MAAM,WAAW,MAAM;IACrB,GAAG,EAAE,IAAI,CAAC;IACV,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB;AAID,MAAM,WAAW,KAAK;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,QAAQ,GAAG,KAAK,GAAG,IAAI,CAAC;IACnC,WAAW,EAAE,QAAQ,GAAG,KAAK,CAAC;IAC9B,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC;IAC7B,YAAY,EAAE,CAAC,GAAG,CAAC,CAAC;CACrB;AAID,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEjE,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,UAAU,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAID,MAAM,MAAM,SAAS,GACjB,MAAM,GACN,aAAa,GACb,YAAY,GACZ,WAAW,GACX,YAAY,GACZ,OAAO,GACP,OAAO,GACP,cAAc,GACd,WAAW,CAAC;AAIhB,MAAM,WAAW,eAAe;IAC9B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB;AAID,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,SAAS,CAAC;IACjB,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,KAAK,CAAC;IACb,KAAK,EAAE,UAAU,CAAC;IAClB,KAAK,EAAE,eAAe,CAAC;IACvB,QAAQ,EAAE,IAAI,CAAC;IACf,eAAe,EAAE,QAAQ,GAAG,KAAK,GAAG,IAAI,CAAC;IACzC,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;CACpB"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ // ── Core types ──────────────────────────────────────────
2
+ export {};
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,2DAA2D"}
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "2d-mini-tennis",
3
+ "version": "1.0.0",
4
+ "description": "A 2D top-down mini tennis game built with React and Canvas",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "prepublishOnly": "npm run build"
13
+ },
14
+ "peerDependencies": {
15
+ "react": ">=18.0.0",
16
+ "react-dom": ">=18.0.0"
17
+ },
18
+ "devDependencies": {
19
+ "@types/react": "^19.0.0",
20
+ "react": "^19.0.0",
21
+ "typescript": "^5.0.0"
22
+ },
23
+ "keywords": [
24
+ "tennis",
25
+ "game",
26
+ "canvas",
27
+ "react",
28
+ "mini-game",
29
+ "2d"
30
+ ],
31
+ "license": "MIT",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/batzorigco/2d-mini-tennis.git"
35
+ }
36
+ }