diff options
author | Kai Stevenson <kai@kaistevenson.com> | 2025-06-28 18:41:15 -0700 |
---|---|---|
committer | Kai Stevenson <kai@kaistevenson.com> | 2025-06-28 18:41:15 -0700 |
commit | ffcdb8b126c5267040ddc8f0304b153fa88755e5 (patch) | |
tree | 2b4dc37b743c12b6175022d68472974f2bcd831b /src/game/index.ts |
local play, some physics
Diffstat (limited to 'src/game/index.ts')
-rw-r--r-- | src/game/index.ts | 113 |
1 files changed, 113 insertions, 0 deletions
diff --git a/src/game/index.ts b/src/game/index.ts new file mode 100644 index 0000000..3ab20ca --- /dev/null +++ b/src/game/index.ts @@ -0,0 +1,113 @@ +import { GAME_SIZE, PADDLE_HEIGHT, PADDLE_WIDTH, SessionState } from "../state"; +import { VELOCITY_SCALING_FACTOR } from "./const"; +import { Action, Collider } from "./types"; +import { applyBallBounce, applyBallVelocity } from "./utils"; + +export const advanceState = async ( + curState: SessionState, + action: Action | undefined +): Promise<SessionState> => { + //simulate network + await new Promise((res) => setTimeout(res, 15)); + + let candidatePaddle = curState.localPlayerGameState.paddle; + + if (action === Action.MOVE_LEFT) { + candidatePaddle.position[0] = Math.max(0, candidatePaddle.position[0] - 1); + } else if (action === Action.MOVE_RIGHT) { + candidatePaddle.position[0] = Math.min( + GAME_SIZE.cols - 1 - PADDLE_WIDTH, + candidatePaddle.position[0] + 1 + ); + } + + let candidateBricks = curState.localPlayerGameState.bricks; + + const colliders: Collider[] = []; + + //paddle collider + colliders.push({ + normal: [0, -1], + boundingBox: [ + { + min: candidatePaddle.position[0], + max: candidatePaddle.position[0] + PADDLE_WIDTH, + }, + { + min: candidatePaddle.position[1], + max: candidatePaddle.position[1] + PADDLE_HEIGHT, + }, + ], + }); + + //brick colliders + candidateBricks.forEach(({ position }, i) => { + colliders.push({ + boundingBox: position.map((pos) => ({ + min: pos - 0.5, + max: pos + 0.5, + })) as Collider["boundingBox"], + normal: [0, 1], + onHit: () => candidateBricks.splice(i, 1), + }); + }); + + //wall colliders + colliders.push( + ...([ + //left wall + { + boundingBox: [ + { min: -1, max: 0 }, + { min: 0, max: GAME_SIZE.rows }, + ], + normal: [1, 0], + }, + //top wall + { + boundingBox: [ + { min: -1, max: GAME_SIZE.cols + 1 }, + { min: -1, max: 0 }, + ], + normal: [0, 1], + }, + //right wall + { + boundingBox: [ + { min: GAME_SIZE.cols, max: GAME_SIZE.cols + 1 }, + { min: 0, max: GAME_SIZE.rows }, + ], + normal: [-1, 0], + }, + ] satisfies Collider[]) + ); + + const candidateBalls = curState.localPlayerGameState.balls + .map((ball) => { + let candidateBall = applyBallVelocity(ball); + + const hitCollider = colliders.find(({ boundingBox }) => + candidateBall.position.every( + (pos, i) => pos >= boundingBox[i].min && pos <= boundingBox[i].max + ) + ); + + if (hitCollider) { + hitCollider.onHit && hitCollider.onHit(); + candidateBall = applyBallBounce(candidateBall, hitCollider.normal); + } + + return candidateBall; + }) + .filter((ball) => !!ball); + + return { + ...curState, + localPlayerGameState: { + ...curState.localPlayerGameState, + bricks: candidateBricks, + paddle: candidatePaddle, + balls: candidateBalls, + }, + }; +}; |