From 9b779598e9df3ac90d410a9276b4383ce30bfbd7 Mon Sep 17 00:00:00 2001 From: George Powell Date: Thu, 26 Mar 2026 12:11:43 -0400 Subject: [PATCH] initial commit --- .gitignore | 34 ++++++++++ CLAUDE.md | 106 +++++++++++++++++++++++++++++++ README.md | 35 +++++++++++ bun.lock | 26 ++++++++ data/names.ts | 3 + data/nicknames.ts | 2 + game/atBatSimulator.ts | 40 ++++++++++++ game/gameRuntime.ts | 30 +++++++++ game/inningSimulator.ts | 15 +++++ index.ts | 1 + package.json | 12 ++++ systems/dialogueGenerator.ts | 1 + systems/hitGenerator.ts | 0 systems/injuryGenerator.ts | 1 + systems/narrationEngine.ts | 8 +++ systems/pitchGenerator.ts | 24 +++++++ test/generateFixtures.ts | 56 +++++++++++++++++ test/testAtBat.ts | 27 ++++++++ test/testPitch.ts | 14 +++++ tsconfig.json | 29 +++++++++ types/BattedBall.ts | 119 +++++++++++++++++++++++++++++++++++ types/GameState.ts | 21 +++++++ types/Player.ts | 30 +++++++++ types/Team.ts | 23 +++++++ types/ThrownPitch.ts | 8 +++ 25 files changed, 665 insertions(+) create mode 100644 .gitignore create mode 100644 CLAUDE.md create mode 100644 README.md create mode 100644 bun.lock create mode 100644 data/names.ts create mode 100644 data/nicknames.ts create mode 100644 game/atBatSimulator.ts create mode 100644 game/gameRuntime.ts create mode 100644 game/inningSimulator.ts create mode 100644 index.ts create mode 100644 package.json create mode 100644 systems/dialogueGenerator.ts create mode 100644 systems/hitGenerator.ts create mode 100644 systems/injuryGenerator.ts create mode 100644 systems/narrationEngine.ts create mode 100644 systems/pitchGenerator.ts create mode 100644 test/generateFixtures.ts create mode 100644 test/testAtBat.ts create mode 100644 test/testPitch.ts create mode 100644 tsconfig.json create mode 100644 types/BattedBall.ts create mode 100644 types/GameState.ts create mode 100644 types/Player.ts create mode 100644 types/Team.ts create mode 100644 types/ThrownPitch.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a14702c --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..764c1dd --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,106 @@ + +Default to using Bun instead of Node.js. + +- Use `bun ` instead of `node ` or `ts-node ` +- Use `bun test` instead of `jest` or `vitest` +- Use `bun build ` instead of `webpack` or `esbuild` +- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install` +- Use `bun run + + +``` + +With the following `frontend.tsx`: + +```tsx#frontend.tsx +import React from "react"; +import { createRoot } from "react-dom/client"; + +// import .css files directly and it works +import './index.css'; + +const root = createRoot(document.body); + +export default function Frontend() { + return

Hello, world!

; +} + +root.render(); +``` + +Then, run index.ts + +```sh +bun --hot ./index.ts +``` + +For more information, read the Bun API docs in `node_modules/bun-types/docs/**.mdx`. diff --git a/README.md b/README.md new file mode 100644 index 0000000..179d8aa --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# softball-server + +> Paisios of Athos threw a weak curveball that hit the dirt. +> Joan of Arc let it land. 1-0. +> Paisios of Athos threw a fastball that hit the outer right corner. +> Joan of Arc swung wildly. 1-1. +> Paisios of Athos tried to throw a slider. It hung in the middle of the strike zone. +> Joan of Arc made contact! The line drive sailed over the infield. Porphyrios of Athos caught it in center field. One out. + +... + +> Paisios of Athos tried to throw a slider. It started inside and broke down and away, missing the zone slightly. +> St. George held his swing. Porphyrios of Athos framed the pitch expertly! It was called a strike! 0-1 +> Paisios of Athos is more confident in his slider. +> Paisios of Athos aimed a four-seam fastball middle-middle. It clipped the outer edge of the zone. +> St. George held his swing. Porphyrios of Athos framed the pitch, but did not get the call. 1-1. + +> George Powell threw a changeup, aiming middle inside. He missed and threw middle-middle. +> Andrew Stankiewicz unloaded confidently, sending a line drive to the gap in the outfield! +> It rolled past Paisios (CF) and was picked up by Porphyrios (LF), who threw to second. +> Wilyer Abreu made it to third. Andrew Stankiewicz stopped at first. + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.3.11. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime. diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..fcfaa1c --- /dev/null +++ b/bun.lock @@ -0,0 +1,26 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "softball-server", + "devDependencies": { + "@types/bun": "latest", + }, + "peerDependencies": { + "typescript": "^5", + }, + }, + }, + "packages": { + "@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="], + + "@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], + + "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + } +} diff --git a/data/names.ts b/data/names.ts new file mode 100644 index 0000000..f9fdac3 --- /dev/null +++ b/data/names.ts @@ -0,0 +1,3 @@ +// fullnames, first names, and last names +// +// fullnames are for prebuilt characters diff --git a/data/nicknames.ts b/data/nicknames.ts new file mode 100644 index 0000000..dcf4796 --- /dev/null +++ b/data/nicknames.ts @@ -0,0 +1,2 @@ +// nicknames based on player stats or things that happened to them +// can happen during a game or after a game diff --git a/game/atBatSimulator.ts b/game/atBatSimulator.ts new file mode 100644 index 0000000..68df69a --- /dev/null +++ b/game/atBatSimulator.ts @@ -0,0 +1,40 @@ +import type { ThrownPitch } from "../types/ThrownPitch"; +import type { Player } from "../types/Player"; +import type { BattedBall } from "../types/BattedBall"; +import type { GameState } from "../types/GameState"; + +import pitchGenerator from "../systems/pitchGenerator"; + +export default function atBatSimulator(batter: Player, catcher: Player, pitcher: Player, game: GameState): GameState { + if (game.outs < 3) { + // do stuff + const pitch: ThrownPitch = pitchGenerator(pitcher); + + // a pitch can either be: + // - batted + // - caught for a ball/strike + // - thrown wild + + } else { + if (game.top) { + game.top = false; + } else { + game.top = true; + game.inning++ // games will keep going on forever + } + } + return game; +} + +function swingSimulator(batter: Player, pitch: ThrownPitch): BattedBall { + // see the ball! + // decide if you're gonna swing! + // aim true! get that timing right! + // return a battedball + return { + exitVelo: 0, + launchAngle: 0, + attackAngle: 0, + spinRate: 0 + }; +} diff --git a/game/gameRuntime.ts b/game/gameRuntime.ts new file mode 100644 index 0000000..e94e91d --- /dev/null +++ b/game/gameRuntime.ts @@ -0,0 +1,30 @@ + +import type { Player } from "../types/Player"; +import type { GameState } from "../types/GameState"; +import type { ThrownPitch } from "../types/ThrownPitch"; + +import pitchGenerator from "../systems/pitchGenerator"; +import type { Team } from "../types/Team"; + +function startGame(homeTeam: Team, awayTeam: Team) { + // simulate +} + +function advanceOrEndGame(game: GameState): GameState { + // home team always bats last + // if it's the top of the 9th and the away team is behind, the game is over + if (game.inning == 9 || game.top == true && game.score.home > game.score.away) console.log("game should be over now."); + + // next inning + if (game.top) { + return { ...game, top: false }; + } else { + return { + ...game, inning: game.inning + 1, top: true + } + } +} + +function endGame(game: GameState) { + console.log("GAME SHOULD END NOW SO FIGURE OUT WHAT TO DO!") +} diff --git a/game/inningSimulator.ts b/game/inningSimulator.ts new file mode 100644 index 0000000..aaefc9e --- /dev/null +++ b/game/inningSimulator.ts @@ -0,0 +1,15 @@ +import type { GameState } from "../types/GameState"; +import type { Team } from "../types/Team"; +import type { Player } from "../types/Player"; +import atBatSimulator from "./atBatSimulator"; + +export default function inningSimulator(battingTeam: Team, fieldingTeam: Team, game: GameState): GameState { + while (game.outs < 3) { + const batter: Player = battingTeam.battingLineup[battingTeam.batterIndex] as Player; + const catcher: Player = fieldingTeam.fieldingLineup.c; + const pitcher: Player = fieldingTeam.fieldingLineup.pitcher; + + game = atBatSimulator(batter, catcher, pitcher, game); + } + return game; +} diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..2a5e4b8 --- /dev/null +++ b/index.ts @@ -0,0 +1 @@ +console.log("Hello via Bun!"); diff --git a/package.json b/package.json new file mode 100644 index 0000000..218f8fb --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "name": "softball-server", + "module": "index.ts", + "type": "module", + "private": true, + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5" + } +} diff --git a/systems/dialogueGenerator.ts b/systems/dialogueGenerator.ts new file mode 100644 index 0000000..2f525e9 --- /dev/null +++ b/systems/dialogueGenerator.ts @@ -0,0 +1 @@ +// dialogue is things the players can say diff --git a/systems/hitGenerator.ts b/systems/hitGenerator.ts new file mode 100644 index 0000000..e69de29 diff --git a/systems/injuryGenerator.ts b/systems/injuryGenerator.ts new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/systems/injuryGenerator.ts @@ -0,0 +1 @@ + diff --git a/systems/narrationEngine.ts b/systems/narrationEngine.ts new file mode 100644 index 0000000..f9c78b7 --- /dev/null +++ b/systems/narrationEngine.ts @@ -0,0 +1,8 @@ + +// pitch narrator +// response narrator (include count) +// fielding narrator +// baserunning narrator +// development narrator +// mood/dialogue narrator +// ambient narrator diff --git a/systems/pitchGenerator.ts b/systems/pitchGenerator.ts new file mode 100644 index 0000000..43ad7d4 --- /dev/null +++ b/systems/pitchGenerator.ts @@ -0,0 +1,24 @@ +// pitchGenerator +import type { Player } from "../types/Player"; +import type { ThrownPitch } from "../types/ThrownPitch"; + +// a pitch can +// be thrown overhand, underhand, with spin that keeps it afloat or makes it a breaking ball, +// or be thrown with no spin (difficult) for a knuckleball. +// it can hit a batter, hit the dirt, or go wild. + +export default function pitchGenerator(pitcher: Player) { + + // what is the pitcher trying to throw? + // what is the count? how good is the batter? what *can* he throw? + + const pitch: ThrownPitch = { + name: 'pitch', + speed: 0, + spinType: "6-12", + rpm: 200, + accuracy: 0.5 + } + + return pitch; +} diff --git a/test/generateFixtures.ts b/test/generateFixtures.ts new file mode 100644 index 0000000..29c038d --- /dev/null +++ b/test/generateFixtures.ts @@ -0,0 +1,56 @@ +import type { Player } from "../types/Player"; +import type { Team } from "../types/Team"; + +const firstNames = ["Alex", "Jordan", "Casey", "Riley", "Morgan", "Taylor", "Drew", "Jamie", "Sam", "Pat"]; +const lastNames = ["Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Wilson", "Moore"]; +const hometowns = ["Austin, TX", "Portland, OR", "Denver, CO", "Nashville, TN", "Raleigh, NC"]; +const genders = ["m", "f", "nb"] as const; + +function rand(min: number, max: number): number { + return Math.random() * (max - min) + min; +} + +function pick(arr: readonly T[]): T { + return arr[Math.floor(Math.random() * arr.length)] as T; +} + +export function randomPlayer(): Player { + return { + gender: pick(genders), + firstname: pick(firstNames), + lastname: pick(lastNames), + hometown: pick(hometowns), + skillset: { + batting: rand(0.2, 1.0), + pitching: { + armStrength: rand(0.2, 1.0), + gripStrength: rand(0.2, 1.0), + fourSeam: rand(0, 1.5), + twoSeam: rand(0, 1.2), + changeup: rand(0, 1.2), + curveball: rand(0, 1.0), + slider: rand(0, 1.0), + splitter: rand(0, 0.8), + knuckleball: rand(0, 0.5), + }, + running: rand(0.2, 1.0), + fielding: rand(0.2, 1.0), + gamesense: rand(0.2, 1.0), + }, + }; +} + +export function randomTeam(): Team { + const battingLineup: Player[] = Array.from({ length: 9 }, randomPlayer); + const [pitcher, c, firstBase, secondBase, thirdBase, ss, lf, cf, rf] = battingLineup as [ + Player, Player, Player, Player, Player, Player, Player, Player, Player + ]; + + return { + battingLineup, + fieldingLineup: { pitcher, c, firstBase, secondBase, thirdBase, ss, lf, cf, rf }, + bullpen: Array.from({ length: 3 }, randomPlayer), + bench: Array.from({ length: 3 }, randomPlayer), + batterIndex: 0, + }; +} diff --git a/test/testAtBat.ts b/test/testAtBat.ts new file mode 100644 index 0000000..304c243 --- /dev/null +++ b/test/testAtBat.ts @@ -0,0 +1,27 @@ +import type { GameState } from "../types/GameState"; +import atBatSimulator from "../game/atBatSimulator"; +import { randomTeam } from "./generateFixtures"; + +const batting = randomTeam(); +const fielding = randomTeam(); + +const initialState: GameState = { + inning: 1, + top: true, + outs: 0, + count: { balls: 0, strikes: 0 }, + score: { home: 0, away: 0 }, +}; + +const batter = batting.battingLineup[batting.batterIndex]!; +const pitcher = fielding.fieldingLineup.pitcher; +const catcher = fielding.fieldingLineup.c; + +console.log(`Batter: ${batter.firstname} ${batter.lastname} (batting: ${batter.skillset.batting.toFixed(2)})`); +console.log(`Pitcher: ${pitcher.firstname} ${pitcher.lastname} (armStrength: ${pitcher.skillset.pitching.armStrength.toFixed(2)})`); +console.log(`Catcher: ${catcher.firstname} ${catcher.lastname}`); +console.log("---"); + +const result = atBatSimulator(batter, catcher, pitcher, initialState); + +console.log("Result state:", result); diff --git a/test/testPitch.ts b/test/testPitch.ts new file mode 100644 index 0000000..0787950 --- /dev/null +++ b/test/testPitch.ts @@ -0,0 +1,14 @@ +import pitchGenerator from "../systems/pitchGenerator"; +import { randomPlayer } from "./generateFixtures"; + +const pitcher = randomPlayer(); + +console.log(`Pitcher: ${pitcher.firstname} ${pitcher.lastname}`); +console.log(` armStrength: ${pitcher.skillset.pitching.armStrength.toFixed(2)}`); +console.log(` gripStrength: ${pitcher.skillset.pitching.gripStrength.toFixed(2)}`); +console.log(` fourSeam: ${pitcher.skillset.pitching.fourSeam.toFixed(2)}`); +console.log("---"); + +const pitch = pitchGenerator(pitcher); + +console.log("ThrownPitch:", pitch); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..bfa0fea --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "Preserve", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +} diff --git a/types/BattedBall.ts b/types/BattedBall.ts new file mode 100644 index 0000000..27e943f --- /dev/null +++ b/types/BattedBall.ts @@ -0,0 +1,119 @@ + +export type BattedBall = { + readonly exitVelo: number; // in mph + readonly launchAngle: number; // vertical angle in degrees + readonly attackAngle: number; // spray angle: pull = -89º, oppo = 89º + readonly spinRate: number; // in RPM // calculated using the bat swing from the batter in the at bat. +}; + +export type BallFlightResult = { + readonly hangTime: number; + readonly distance: number; + readonly landingHorizontalVelo: number; +}; + +/** + * Calculates baseball flight trajectory including: + * 1. Aerodynamic Drag (Sea Level) + * 2. Magnus Effect (Lift from spin) + * 3. Ground Friction (Impact physics based on spin type) + */ +export function calculateFullFlightPath( + ball: BattedBall, + surfaceFriction: number = 0.4 // 0.3 for turf, 0.4 for grass, 0.45 for dirt +): BallFlightResult { + // Physical Constants (Imperial: slugs, feet, seconds) + const G = 32.174; + const RHO = 0.0023769; // Air density at sea level + const CD = 0.35; // Drag coefficient + const AREA = 0.0458; // Cross-sectional area (sq ft) + const MASS = 0.00994; // Mass (slugs) + const RADIUS = 0.121; // Radius (ft) + const E = 0.5; // Coefficient of restitution (bounciness) + + const MPH_TO_FPS = 1.46667; + const FPS_TO_MPH = 0.681818; + const DT = 0.005; // Smaller time step for better accuracy + + // Initial State + let t = 0; + let x = 0; + let y = 3; // Standard contact height (ft) + let z = 0; + + const v0 = ball.exitVelo * MPH_TO_FPS; + const theta = (ball.launchAngle * Math.PI) / 180; + const phi = (ball.attackAngle * Math.PI) / 180; + const omega = (ball.spinRate * 2 * Math.PI) / 60; // RPM to Rad/s + + // Velocity Components + let vy = v0 * Math.sin(theta); + let v_horiz_initial = v0 * Math.cos(theta); + let vx = v_horiz_initial * Math.cos(phi); + let vz = v_horiz_initial * Math.sin(phi); + + // 1. Flight Simulation (Numerical integration using Euler's Method) + while (y > 0) { + const v = Math.sqrt(vx * vx + vy * vy + vz * vz); + if (v < 0.1) break; + + // Aerodynamics: Drag and Magnus (Lift) + const spinFactor = (RADIUS * omega) / v; + const CL = 1 / (2 + (1 / spinFactor)); + + const dragAccelFactor = (0.5 * RHO * v * CD * AREA) / MASS; + const liftAccelFactor = (0.5 * RHO * v * CL * AREA) / MASS; + + // Acceleration Vectors + // Lift acts perpendicular to velocity; for backspin, it provides upward lift + const ax = -dragAccelFactor * vx - liftAccelFactor * (vy / v) * Math.cos(phi); + const ay = -G - (dragAccelFactor * vy) + liftAccelFactor * (vx / v); + const az = -dragAccelFactor * vz; + + // Update State + vx += ax * DT; + vy += ay * DT; + vz += az * DT; + x += vx * DT; + y += vy * DT; + z += vz * DT; + t += DT; + + if (t > 15) break; // Safety timeout + } + + // 2. Landing Physics (Impact Friction) + // Horizontal velocity at the moment of impact + const vx_impact = vx; + const vz_impact = vz; + const vy_impact = Math.abs(vy); + const v_horiz_impact = Math.sqrt(vx_impact * vx_impact + vz_impact * vz_impact); + + /** + * Slip Velocity Calculation: + * v_slip = v_horizontal + (omega * radius) + * Positive omega (backspin) increases slip, causing friction to act against motion. + * Negative omega (topspin) decreases slip, potentially making it negative, + * which causes friction to accelerate the ball forward. + */ + const v_slip = v_horiz_impact + (omega * RADIUS); + + // Horizontal Impulse (Change in velocity due to friction) + const delta_v = surfaceFriction * vy_impact * (1 + E); + + let finalV_horiz: number; + if (v_slip > 0) { + // Friction acts as a braking force (Backspin) + finalV_horiz = v_horiz_impact - delta_v; + } else { + // Friction acts as an accelerating force (Topspin) + finalV_horiz = v_horiz_impact + delta_v; + } + + // Results + return { + hangTime: Number(t.toFixed(2)), + distance: Number(Math.sqrt(x * x + z * z).toFixed(2)), + landingHorizontalVelo: Number(Math.max(0, finalV_horiz * FPS_TO_MPH).toFixed(2)) + }; +} diff --git a/types/GameState.ts b/types/GameState.ts new file mode 100644 index 0000000..a614b5c --- /dev/null +++ b/types/GameState.ts @@ -0,0 +1,21 @@ +import type { Team } from "./Team"; + +export type GameState = { + readonly inning: number; + readonly top: boolean; + readonly count: AtBat; + readonly outs: number; + readonly score: GameScore; + // homeTeam: Team; + // awayTeam: Team; +} + +type AtBat = { + readonly balls: number; + readonly strikes: number; +} + +type GameScore = { + readonly home: number; + readonly away: number; +} diff --git a/types/Player.ts b/types/Player.ts new file mode 100644 index 0000000..368a312 --- /dev/null +++ b/types/Player.ts @@ -0,0 +1,30 @@ +// A softball player + +export type Player = { + readonly gender: "m" | "f" | "nb"; + readonly firstname: string; + readonly lastname: string; + readonly hometown: string; + readonly skillset: Skillset; +} + +type Skillset = { + readonly batting: number; + readonly pitching: PitchingSkillset; + readonly running: number; + readonly fielding: number; // micro; muscle memory, movement, athleticism, etc. + readonly gamesense: number; // macro +} + +type PitchingSkillset = { // the skill a player has in pitching + // Player.pitching.armStrength, for example + readonly armStrength: number; + readonly gripStrength: number; + readonly fourSeam: number; // starts at 0. a skill above 1 means a player "knows" a pitch. + readonly twoSeam: number; + readonly changeup: number; + readonly curveball: number; + readonly slider: number; + readonly splitter: number; + readonly knuckleball: number; +} diff --git a/types/Team.ts b/types/Team.ts new file mode 100644 index 0000000..53d4fdc --- /dev/null +++ b/types/Team.ts @@ -0,0 +1,23 @@ +import type { Player } from "./Player"; + +export type Team = { + // how to organize a team's list of batters, pitchers, bench players, etc.? + readonly battingLineup: readonly Player[]; + readonly fieldingLineup: FieldingLineup; + readonly bullpen: readonly Player[]; + readonly bench: readonly Player[]; + readonly batterIndex: number; + // where to keep track of players no longer in the game? +} + +type FieldingLineup = { + readonly pitcher: Player; + readonly c: Player; + readonly firstBase: Player; + readonly secondBase: Player; + readonly thirdBase: Player; + readonly ss: Player; + readonly lf: Player; + readonly cf: Player; + readonly rf: Player; +} diff --git a/types/ThrownPitch.ts b/types/ThrownPitch.ts new file mode 100644 index 0000000..8aecfb0 --- /dev/null +++ b/types/ThrownPitch.ts @@ -0,0 +1,8 @@ + +export type ThrownPitch = { + readonly name: string; + readonly speed: number; // in mph + readonly spinType: "6-12" | "4-10" | "3-9" | "12-6" | "9-3" | "none"; + readonly rpm: number; // example: 6-12 with fast rpm is fastball, 6-12 with slow rpm is changeup + readonly accuracy: number; // how likely that a ball will end up where the pitcher meant for it to. +}