import { createRandomBall, Event, GAME_SIZE, PADDLE_HEIGHT, PADDLE_WIDTH, SessionState, } from "../state"; import { Action, Collider } from "./types"; import { applyBallBounce, applyBallVelocity } from "./utils"; export const advanceStateLocal = async ( curState: SessionState, action: Action | undefined ): Promise => { //minimum delay await new Promise((res) => setTimeout(res, 15)); let candidateOutboundEventQueue: Event[] = []; 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: () => { candidateOutboundEventQueue.push({ name: "NEW_BALL" }); 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); //apply incoming events for (const event of curState.inboundEventQueue) { switch (event.name) { case "NEW_BALL": candidateBalls.push(createRandomBall()); } } return { ...curState, inboundEventQueue: [], outboundEventQueue: candidateOutboundEventQueue, localPlayerGameState: { ...curState.localPlayerGameState, bricks: candidateBricks, paddle: candidatePaddle, balls: candidateBalls, }, seqno: curState.seqno + 1, }; };