Skip to content

Commit

Permalink
enhance gift mechanics; add gift rendering, throwing events, and upda…
Browse files Browse the repository at this point in the history
…te logic for gift states
  • Loading branch information
gtanczyk committed Dec 1, 2024
1 parent 66b0af0 commit 4209cec
Show file tree
Hide file tree
Showing 11 changed files with 847 additions and 33 deletions.
92 changes: 79 additions & 13 deletions games/xmas/src/screens/play/game-controller.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { RefObject } from 'react';
import { RefObject, useEffect } from 'react';
import { useCustomEvent } from '../../utils/custom-events';
import { GameWorldState } from './game-world/game-world-types';
import { RenderState } from './game-render/render-state';
import { addFireballFromSanta, moveSanta, setSantaDirection } from './game-world/game-world-manipulate';
import { addFireballFromSanta, moveSanta, setSantaDirection, throwGift } from './game-world/game-world-manipulate';
import {
GameEvents,
MoveSantaEvent,
SetSantaDirectionEvent,
StartChargingEvent,
StopChargingEvent,
StartThrowingGiftEvent,
StopThrowingGiftEvent,
} from './game-input/input-events';

type GameControllerProps = {
Expand All @@ -19,6 +21,21 @@ type GameControllerProps = {
};

export function GameController({ gameStateRef }: GameControllerProps) {
// Notify input controller about Santa's gift state
useEffect(() => {
const interval = setInterval(() => {
if (!gameStateRef.current) return;
const { playerSanta } = gameStateRef.current.gameWorldState;
window.dispatchEvent(
new CustomEvent('santa:stateChange', {
detail: { hasGift: !!playerSanta.carriedGift }
})
);
}, 100);

return () => clearInterval(interval);
}, [gameStateRef]);

useCustomEvent<MoveSantaEvent>(GameEvents.MOVE_SANTA, (event) => {
if (!gameStateRef.current) return;
moveSanta(gameStateRef.current.gameWorldState.playerSanta, event.input);
Expand All @@ -29,28 +46,77 @@ export function GameController({ gameStateRef }: GameControllerProps) {
setSantaDirection(gameStateRef.current.gameWorldState.playerSanta, event.direction);
});

// Fireball charging handlers
useCustomEvent<StartChargingEvent>(GameEvents.START_CHARGING, (event) => {
if (!gameStateRef.current) return;
moveSanta(gameStateRef.current.gameWorldState.playerSanta, {
charging: true,
chargeStartTime: event.timestamp,
});
const { playerSanta } = gameStateRef.current.gameWorldState;

// Only start charging if not carrying a gift
if (!playerSanta.carriedGift) {
moveSanta(playerSanta, {
charging: true,
chargeStartTime: event.timestamp,
});
}
});

useCustomEvent<StopChargingEvent>(GameEvents.STOP_CHARGING, (event) => {
if (!gameStateRef.current) return;
const { playerSanta } = gameStateRef.current.gameWorldState;
const { chargeStartTime } = playerSanta.input;

moveSanta(playerSanta, {
charging: false,
chargeStartTime: null,
});
// Only process fireball charging if not carrying a gift
if (!playerSanta.carriedGift && chargeStartTime !== null) {
moveSanta(playerSanta, {
charging: false,
chargeStartTime: null,
});

addFireballFromSanta(
gameStateRef.current.gameWorldState,
playerSanta,
event.timestamp - chargeStartTime
);
}
});

// Gift throwing handlers
useCustomEvent<StartThrowingGiftEvent>(GameEvents.START_THROWING_GIFT, (event) => {
if (!gameStateRef.current) return;
const { playerSanta } = gameStateRef.current.gameWorldState;

// Only start gift throwing if carrying a gift
if (playerSanta.carriedGift) {
moveSanta(playerSanta, {
charging: true,
chargeStartTime: event.timestamp,
});
}
});

useCustomEvent<StopThrowingGiftEvent>(GameEvents.STOP_THROWING_GIFT, (event) => {
if (!gameStateRef.current) return;
const { playerSanta } = gameStateRef.current.gameWorldState;
const { chargeStartTime } = playerSanta.input;

// Only process gift throwing if carrying a gift and charging was started
if (playerSanta.carriedGift && chargeStartTime !== null) {
const chargeTime = event.timestamp - chargeStartTime;

// Reset input state first
moveSanta(playerSanta, {
charging: false,
chargeStartTime: null,
});

if (chargeStartTime !== null) {
addFireballFromSanta(gameStateRef.current.gameWorldState, playerSanta, event.timestamp - chargeStartTime);
// Throw the gift
throwGift(
gameStateRef.current.gameWorldState,
playerSanta,
chargeTime
);
}
});

return null;
}
}
75 changes: 59 additions & 16 deletions games/xmas/src/screens/play/game-input/input-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
SetSantaDirectionEvent,
StartChargingEvent,
StopChargingEvent,
StartThrowingGiftEvent,
StopThrowingGiftEvent,
} from './input-events';

const INPUT_KEYS = {
Expand All @@ -24,6 +26,7 @@ type InputState = {
down: boolean;
};
isCharging: boolean;
isThrowingGift: boolean;
chargeStartTime: number | null;
};

Expand All @@ -35,9 +38,13 @@ const initialInputState: InputState = {
down: false,
},
isCharging: false,
isThrowingGift: false,
chargeStartTime: null,
};

// External state to track if Santa is carrying a gift
let isCarryingGift = false;

export function InputController() {
const [inputState, setInputState] = useState<InputState>(initialInputState);

Expand All @@ -51,26 +58,62 @@ export function InputController() {
}
}, []);

// Subscribe to santa state changes
useEffect(() => {
const handleSantaStateChange = (event: CustomEvent) => {
if (event.detail && 'hasGift' in event.detail) {
isCarryingGift = event.detail.hasGift;
}
};

window.addEventListener('santa:stateChange', handleSantaStateChange as EventListener);
return () => {
window.removeEventListener('santa:stateChange', handleSantaStateChange as EventListener);
};
}, []);

const handleKeyboardInput = useCallback(
(event: KeyboardEvent, isKeyDown: boolean) => {
// Handle charging input
// Handle charging/throwing input
if (INPUT_KEYS.CHARGE.has(event.key)) {
if (isKeyDown && !inputState.isCharging) {
if (isKeyDown && !inputState.isCharging && !inputState.isThrowingGift) {
const timestamp = Date.now();
setInputState(prev => ({
...prev,
isCharging: true,
chargeStartTime: timestamp,
}));
dispatchCustomEvent<StartChargingEvent>(GameEvents.START_CHARGING, { timestamp });
} else if (!isKeyDown && inputState.isCharging) {

// Determine whether to start charging fireball or throwing gift
if (isCarryingGift) {
setInputState(prev => ({
...prev,
isThrowingGift: true,
chargeStartTime: timestamp,
}));
dispatchCustomEvent<StartThrowingGiftEvent>(GameEvents.START_THROWING_GIFT, { timestamp });
} else {
setInputState(prev => ({
...prev,
isCharging: true,
chargeStartTime: timestamp,
}));
dispatchCustomEvent<StartChargingEvent>(GameEvents.START_CHARGING, { timestamp });
}
} else if (!isKeyDown) {
const timestamp = Date.now();
setInputState(prev => ({
...prev,
isCharging: false,
chargeStartTime: null,
}));
dispatchCustomEvent<StopChargingEvent>(GameEvents.STOP_CHARGING, { timestamp });

// Handle release based on current state
if (inputState.isThrowingGift) {
setInputState(prev => ({
...prev,
isThrowingGift: false,
chargeStartTime: null,
}));
dispatchCustomEvent<StopThrowingGiftEvent>(GameEvents.STOP_THROWING_GIFT, { timestamp });
} else if (inputState.isCharging) {
setInputState(prev => ({
...prev,
isCharging: false,
chargeStartTime: null,
}));
dispatchCustomEvent<StopChargingEvent>(GameEvents.STOP_CHARGING, { timestamp });
}
}
return;
}
Expand Down Expand Up @@ -98,7 +141,7 @@ export function InputController() {
dispatchMovement(newMoveInput);
}
},
[inputState.moveInput, inputState.isCharging, dispatchMovement],
[inputState.moveInput, inputState.isCharging, inputState.isThrowingGift, dispatchMovement],
);

useEffect(() => {
Expand Down
24 changes: 24 additions & 0 deletions games/xmas/src/screens/play/game-input/input-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ export enum GameEvents {
START_CHARGING = 'game:start-charging',
STOP_CHARGING = 'game:stop-charging',

// Gift throwing events
START_THROWING_GIFT = 'game:start-throwing-gift',
STOP_THROWING_GIFT = 'game:stop-throwing-gift',

// Santa movement events
MOVE_SANTA = 'game:move-santa',
SET_SANTA_DIRECTION = 'game:set-santa-direction',
Expand All @@ -18,6 +22,14 @@ export type StopChargingEvent = {
timestamp: number;
};

export type StartThrowingGiftEvent = {
timestamp: number;
};

export type StopThrowingGiftEvent = {
timestamp: number;
};

// Santa Movement Events
export type MoveSantaEvent = {
input: {
Expand Down Expand Up @@ -81,3 +93,15 @@ export function isStopChargingEvent(event: unknown): event is StopChargingEvent

return hasProperty(event, 'timestamp') && typeof event.timestamp === 'number';
}

export function isStartThrowingGiftEvent(event: unknown): event is StartThrowingGiftEvent {
if (!isObject(event)) return false;

return hasProperty(event, 'timestamp') && typeof event.timestamp === 'number';
}

export function isStopThrowingGiftEvent(event: unknown): event is StopThrowingGiftEvent {
if (!isObject(event)) return false;

return hasProperty(event, 'timestamp') && typeof event.timestamp === 'number';
}
6 changes: 5 additions & 1 deletion games/xmas/src/screens/play/game-render/game-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { renderSnow } from './snow-renderer';
import { renderSanta } from './santa-renderer';
import { renderFireballs } from './fireball-renderer';
import { renderSky } from './landscape/sky/sky-renderer';
import { renderGifts } from './gift-renderer';

export const renderGame = (ctx: CanvasRenderingContext2D, world: GameWorldState, renderState: RenderState) => {
const { viewport } = renderState;
Expand All @@ -22,6 +23,9 @@ export const renderGame = (ctx: CanvasRenderingContext2D, world: GameWorldState,
renderLandscape(ctx, renderState.landscape, renderState.viewport);
renderSnow(ctx, renderState);

// Render gifts before Santas for proper depth
renderGifts(ctx, world.gifts, world.time);

// Render all santas
world.santas.forEach((santa) => {
renderSanta(ctx, santa, world.time);
Expand All @@ -31,4 +35,4 @@ export const renderGame = (ctx: CanvasRenderingContext2D, world: GameWorldState,

// Restore the context state
ctx.restore();
};
};
Loading

0 comments on commit 4209cec

Please sign in to comment.