summaryrefslogtreecommitdiff
path: root/src/game/index.ts
blob: 3ab20caf588c710661d1f8f256c33dc3bebc170a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
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,
    },
  };
};