File size: 5,583 Bytes
2d54981
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// Collision detection system

import { PlayerState, Block, Enemy, Position } from './types';
import { GAME_CONFIG } from '../constants';

const { TILE_SIZE } = GAME_CONFIG;

export interface CollisionResult {
    collided: boolean;
    side?: 'top' | 'bottom' | 'left' | 'right';
    block?: Block;
}

export class Collision {
    // AABB collision check between two rectangles
    checkAABB(
        a: { x: number; y: number; width: number; height: number },
        b: { x: number; y: number; width: number; height: number }
    ): boolean {
        return (
            a.x < b.x + b.width &&
            a.x + a.width > b.x &&
            a.y < b.y + b.height &&
            a.y + a.height > b.y
        );
    }

    // Determine collision side
    getCollisionSide(
        player: PlayerState,
        block: Block,
        prevY: number
    ): 'top' | 'bottom' | 'left' | 'right' {
        const playerBottom = player.y + player.height;
        const playerTop = player.y;
        const blockBottom = block.y + block.height;
        const blockTop = block.y;

        // Check if landing on top
        if (prevY + player.height <= block.y + 2 && player.vy >= 0) {
            return 'top';
        }

        // Check if hitting from below
        if (prevY >= blockBottom - 2 && player.vy < 0) {
            return 'bottom';
        }

        // Determine left/right based on position
        const playerCenterX = player.x + player.width / 2;
        const blockCenterX = block.x + block.width / 2;

        return playerCenterX < blockCenterX ? 'right' : 'left';
    }

    // Check player collision with blocks
    checkPlayerBlockCollision(
        player: PlayerState,
        blocks: Block[],
        prevY: number
    ): { grounded: boolean; hitBlock?: Block } {
        let grounded = false;
        let hitBlock: Block | undefined;

        for (const block of blocks) {
            // Skip coins - they have separate handling
            if (block.type === 'coin') continue;

            if (this.checkAABB(player, block)) {
                const side = this.getCollisionSide(player, block, prevY);

                switch (side) {
                    case 'top':
                        player.y = block.y - player.height;
                        player.vy = 0;
                        player.isJumping = false;
                        player.isFalling = false;
                        grounded = true;
                        break;
                    case 'bottom':
                        player.y = block.y + block.height;
                        player.vy = 1; // Small downward velocity
                        if (block.type === 'question' && !block.isHit) {
                            hitBlock = block;
                        }
                        break;
                    case 'left':
                        player.x = block.x + block.width;
                        player.vx = 0;
                        break;
                    case 'right':
                        player.x = block.x - player.width;
                        player.vx = 0;
                        break;
                }
            }
        }

        return { grounded, hitBlock };
    }

    // Check enemy collision with blocks (ground only)
    checkEnemyBlockCollision(enemy: Enemy, blocks: Block[]): void {
        for (const block of blocks) {
            if (block.type === 'coin') continue;

            if (this.checkAABB(enemy, block)) {
                // Land on top
                if (enemy.vy > 0) {
                    enemy.y = block.y - enemy.height;
                    enemy.vy = 0;
                }
                // Side collision - reverse direction
                if (
                    enemy.x + enemy.width > block.x &&
                    enemy.x < block.x + block.width
                ) {
                    if (enemy.y + enemy.height > block.y + 4) {
                        enemy.direction *= -1;
                    }
                }
            }
        }
    }

    // Check player collision with enemy
    checkPlayerEnemyCollision(
        player: PlayerState,
        enemy: Enemy
    ): { killed: boolean; playerHit: boolean } {
        if (!enemy.isAlive || !player.isAlive) {
            return { killed: false, playerHit: false };
        }

        if (this.checkAABB(player, enemy)) {
            // Check if player is landing on enemy (stomping)
            if (player.vy > 0 && player.y + player.height < enemy.y + enemy.height / 2) {
                return { killed: true, playerHit: false };
            } else {
                return { killed: false, playerHit: true };
            }
        }

        return { killed: false, playerHit: false };
    }

    // Check player collision with coin
    checkCoinCollision(player: PlayerState, blocks: Block[]): Block[] {
        const collectedCoins: Block[] = [];

        blocks.forEach((block, index) => {
            if (block.type === 'coin' && this.checkAABB(player, block)) {
                collectedCoins.push(block);
            }
        });

        return collectedCoins;
    }

    // Check if player reached the flag
    checkFlagCollision(player: PlayerState, flagPosition: Position): boolean {
        return (
            player.x + player.width >= flagPosition.x &&
            player.x <= flagPosition.x + 50 &&
            player.y <= flagPosition.y + 100
        );
    }

    // Check if player fell off the level
    checkFallDeath(player: PlayerState, levelHeight: number): boolean {
        return player.y > levelHeight + 100;
    }
}