Skip to content

Commit

Permalink
add ambush action to lion entity; update state machine and action bar…
Browse files Browse the repository at this point in the history
… for new mechanics
  • Loading branch information
gtanczyk committed Jan 23, 2025
1 parent d697ecf commit 7767452
Show file tree
Hide file tree
Showing 18 changed files with 180 additions and 34 deletions.
32 changes: 32 additions & 0 deletions games/hungry-lion/GENAICODE_INSTRUCTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,4 +205,36 @@ When implementing natural prey movement:
4. Ensure smooth integration with existing collision detection and boundary handling
5. Maintain clear state transitions between different behaviors

### Ambush State Mechanics
When implementing ambush state and mechanics:
1. State Implementation:
- Create dedicated state file for ambush mechanics
- Implement no movement logic during ambush
- Handle proper state transitions
- Include speed boost flag in state data

2. Speed Boost Implementation:
- Define separate constants for normal and boosted speeds
- Implement boost duration with proper timing
- Handle boost expiration gracefully
- Ensure smooth acceleration transitions

3. Action Management:
- Make ambush action mutually exclusive with others
- Clear other actions when enabling ambush
- Handle proper state cleanup on action toggle
- Maintain clear action state transitions

4. State Transitions:
- Handle transitions between all relevant states
- Implement proper boost timing on state change
- Clear boost effects when appropriate
- Maintain state consistency during transitions

5. Game Balance:
- Configure appropriate boost values
- Set reasonable boost duration
- Balance risk/reward of ambush state
- Consider prey behavior modifications

Remember: Documentation is as important as code. Every code change should be reflected in the project's documentation to maintain clarity and facilitate future development.
10 changes: 10 additions & 0 deletions games/hungry-lion/GENAICODE_TRACKER.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@
* ✅ Added visual feedback for hunger levels
* ✅ Fixed Initial Hunger Mechanics
* ✅ Hunger Bar and Starvation Warnings
* ✅ Ambush Mechanics
* ✅ Added 'ambush' action button to action bar
* ✅ Implemented LION_AMBUSH state with no movement
* ✅ Added speed boost when transitioning from ambush to chase
* ✅ Made ambush action mutually exclusive with walk/attack
* ✅ Implemented state transitions and boost duration
* ✅ Added proper type definitions and documentation
* ☐ Add visual feedback for ambush state
* ☐ Implement reduced visibility effect on prey behavior
* ☐ Add particle effects for speed boost
* ☐ Stealth movement mechanics
* ✅ Action Bar Implementation
* ✅ Basic action bar UI
Expand Down
6 changes: 5 additions & 1 deletion games/hungry-lion/src/screens/play/components/action-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ interface ActionBarProps {
actions: {
walk: { enabled: boolean };
attack: { enabled: boolean };
ambush: { enabled: boolean };
};
}

export function ActionBar({ actions }: ActionBarProps) {
const toggleAction = (action: 'walk' | 'attack') => {
const toggleAction = (action: 'walk' | 'attack' | 'ambush') => {
dispatchCustomEvent<ToggleActionEvent>(GameEvents.TOGGLE_ACTION, {
action,
enabled: !actions[action].enabled,
Expand All @@ -25,6 +26,9 @@ export function ActionBar({ actions }: ActionBarProps) {
<ActionButton active={actions.attack.enabled} onClick={() => toggleAction('attack')}>
Attack
</ActionButton>
<ActionButton active={actions.ambush.enabled} onClick={() => toggleAction('ambush')}>
Ambush
</ActionButton>
</ActionBarContainer>
);
}
Expand Down
4 changes: 4 additions & 0 deletions games/hungry-lion/src/screens/play/game-controller.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ export function GameController({ gameStateRef }: GameControllerProps) {
const lion = getPlayerLion(gameStateRef.current.gameWorldState);
if (lion) {
lion.actions[event.action].enabled = event.enabled;
// When enabling ambush, disable other actions
if (event.action === 'ambush' && event.enabled) {
lion.actions.walk.enabled = lion.actions.attack.enabled = false;
}
if (!event.enabled) {
handleCancelChase();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export type CancelChaseEvent = {
};

export type ToggleActionEvent = {
action: 'walk' | 'attack';
action: 'walk' | 'attack' | 'ambush';
enabled: boolean;
};

Expand Down
1 change: 1 addition & 0 deletions games/hungry-lion/src/screens/play/game-viewport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ export function GameViewport({ gameStateRef, children }: PropsWithChildren<GameV
getPlayerLion(gameStateRef.current.gameWorldState)?.actions || {
walk: { enabled: false },
attack: { enabled: false },
ambush: { enabled: false },
}
}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface LionEntity extends Entity {
actions: {
walk: { enabled: boolean };
attack: { enabled: boolean };
ambush: { enabled: boolean };
};
stateMachine: [StateType, StateData];
}
Expand All @@ -53,4 +54,4 @@ export interface CarrionEntity extends Entity {
export type Entities = {
entities: Map<EntityId, Entity>;
nextEntityId: EntityId;
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export function createEntities(): Entities {
actions: {
walk: { enabled: false },
attack: { enabled: false },
ambush: { enabled: false },
},
stateMachine: createLionStateMachine(),
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export interface State<T extends Entity = Entity, D extends BaseStateData = Base
/** Function to update the state */
update: StateUpdate<T, D>;
/** Optional function called when entering the state */
onEnter?: (context: StateContext<T>) => D;
onEnter?: (context: StateContext<T>, nextData: D) => D;
/** Optional function called when leaving the state */
onExit?: (context: StateContext<T>, nextState: StateType) => void;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function stateUpdate(type: StateType, data: StateData, context: StateCont

// If state is changing, handle the transition
if (isStateTransitionNeeded(type, nextState)) {
return [nextState, handleStateTransition(currentState, nextState, context, STATES)];
return [nextState, handleStateTransition(currentState, nextState, nextData, context, STATES)];
}

return [nextState, nextData];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { State, StateContext, StateData, StateType } from './state-machine-types
export function handleStateTransition<T extends Entity>(
currentState: State<T, StateData>,
nextStateType: StateType,
nextData: StateData,
context: StateContext<T>,
states: State<T, StateData>[],
): StateData {
Expand All @@ -22,11 +23,11 @@ export function handleStateTransition<T extends Entity>(
const nextStateHandler = states.find(({ id }) => id === nextStateType) as State<T, StateData>;
if (nextStateHandler?.onEnter) {
// Call enter handler and use its data
return nextStateHandler.onEnter(context);
return nextStateHandler.onEnter(context, nextData);
}

// Return default state data if no enter handler
return { enteredAt: context.updateContext.gameState.time };
return { ...nextData, enteredAt: context.updateContext.gameState.time };
}

/**
Expand Down Expand Up @@ -82,4 +83,4 @@ export function isStateTransitionNeeded(currentStateType: StateType, nextStateTy
*/
export function logStateHandlerNotFound(stateType: StateType): void {
console.warn(`No handler found for state: ${stateType}`);
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { LION_IDLE_STATE, LionIdleStateData } from './lion-idle-state';
import { LION_MOVING_TO_TARGET_STATE } from './lion-moving-state';
import { LION_CHASING_STATE } from './lion-chasing-state';
import { LION_AMBUSH_STATE } from './lion-ambush-state';

// Re-export all states
export { LION_IDLE_STATE, LION_MOVING_TO_TARGET_STATE, LION_CHASING_STATE };
export { LION_IDLE_STATE, LION_MOVING_TO_TARGET_STATE, LION_CHASING_STATE, LION_AMBUSH_STATE };

// Export lion state types
export type LionStateType = 'LION_IDLE' | 'LION_MOVING_TO_TARGET' | 'LION_CHASING';
export type LionStateType = 'LION_IDLE' | 'LION_MOVING_TO_TARGET' | 'LION_CHASING' | 'LION_AMBUSH';

// Export combined array of all lion states
export const LION_STATES = [LION_IDLE_STATE, LION_MOVING_TO_TARGET_STATE, LION_CHASING_STATE];
export const LION_STATES = [LION_IDLE_STATE, LION_MOVING_TO_TARGET_STATE, LION_CHASING_STATE, LION_AMBUSH_STATE];

/**
* Creates initial lion state machine state
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { LionEntity } from '../../../entities/entities-types';
import { BaseStateData, State } from '../../state-machine-types';

/**
* State data interface for lion ambush state
*/
export interface LionAmbushStateData extends BaseStateData {
/** Flag to indicate if speed boost should be applied when transitioning to chasing */
applySpeedBoost?: boolean;
}

/**
* Lion ambush state implementation
* In this state:
* - Lion does not move
* - Lion is less noticeable by prey (handled by prey behavior)
* - Transitions to chasing state with speed boost when target acquired
*/
export const LION_AMBUSH_STATE: State<LionEntity, LionAmbushStateData> = {
id: 'LION_AMBUSH',

update: (data, context) => {
const { entity } = context;

// Return to idle if ambush action is disabled
if (!entity.actions.ambush.enabled) {
return {
nextState: 'LION_IDLE',
data: { enteredAt: context.updateContext.gameState.time },
};
}

// If we have a target and attack is enabled, transition to chasing with speed boost
if (entity.target.entityId && entity.actions.attack.enabled) {
entity.actions.ambush.enabled = false;
return {
nextState: 'LION_CHASING',
data: {
enteredAt: context.updateContext.gameState.time,
applySpeedBoost: true, // Set flag for speed boost
boostAppliedAt: context.updateContext.gameState.time, // Set timestamp for speed boost
},
};
}

// In ambush state:
// - No movement (zero acceleration and velocity)
// - Keep target direction updated if we have a target
entity.acceleration = 0;

// Update direction to face target if one exists
if (entity.target.entityId) {
const targetEntity = context.updateContext.gameState.entities.entities.get(entity.target.entityId);
if (targetEntity) {
const dx = targetEntity.position.x - entity.position.x;
const dy = targetEntity.position.y - entity.position.y;
entity.targetDirection = Math.atan2(dy, dx);
entity.acceleration = 0.001;
}
}

// Stay in ambush state
return {
nextState: 'LION_AMBUSH',
data: {
...data,
lastTargetUpdate: context.updateContext.gameState.time,
},
};
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@ import { BaseStateData, State } from '../../state-machine-types';
import { moveTowardsTarget } from './lion-state-utils';

// Constants for lion chasing behavior
const CHASE_ACCELERATION = 0.01;
const CHASE_ACCELERATION = 0.01; // Base acceleration when chasing
const CHASE_BOOST_ACCELERATION = 0.02; // Boosted acceleration when coming from ambush
const BOOST_DURATION = 1000; // Duration of speed boost in milliseconds

/**
* State data interface for lion chasing state
*/
export type LionChasingStateData = BaseStateData;
export interface LionChasingStateData extends BaseStateData {
/** Flag indicating if speed boost should be applied */
applySpeedBoost?: boolean;
/** Timestamp when the boost was applied */
boostAppliedAt?: number;
}

/**
* Lion chasing state implementation
Expand All @@ -18,13 +25,14 @@ export const LION_CHASING_STATE: State<LionEntity, LionChasingStateData> = {

update: (data, context) => {
const { entity } = context;
const currentTime = context.updateContext.gameState.time;

// Return to idle if attack action is disabled
if (!entity.actions.attack.enabled) {
entity.target = {}; // Clear target
return {
nextState: 'LION_IDLE',
data: { enteredAt: context.updateContext.gameState.time },
data: { enteredAt: currentTime },
};
}

Expand All @@ -36,25 +44,38 @@ export const LION_CHASING_STATE: State<LionEntity, LionChasingStateData> = {
entity.target = {}; // Clear target
return {
nextState: 'LION_IDLE',
data: { enteredAt: context.updateContext.gameState.time },
data: { enteredAt: currentTime },
};
}

// Move towards target with higher acceleration
moveTowardsTarget(entity, targetEntity.position.x, targetEntity.position.y, CHASE_ACCELERATION);
// Calculate acceleration based on boost status
let acceleration = CHASE_ACCELERATION;
if (data.applySpeedBoost && data.boostAppliedAt) {
const boostElapsed = currentTime - data.boostAppliedAt;
if (boostElapsed < BOOST_DURATION) {
acceleration = CHASE_BOOST_ACCELERATION;
} else {
// Reset boost after duration expires
data.applySpeedBoost = false;
data.boostAppliedAt = undefined;
}
}

// Move towards target with calculated acceleration
moveTowardsTarget(entity, targetEntity.position.x, targetEntity.position.y, acceleration);

return {
nextState: 'LION_CHASING',
data: {
...data,
targetEntityId: entity.target.entityId,
lastTargetUpdate: context.updateContext.gameState.time,
lastTargetUpdate: currentTime,
},
};
},

onEnter: (context) => ({
enteredAt: context.updateContext.gameState.time,
onEnter: (context, nextData) => ({
targetEntityId: context.entity.target.entityId,
...nextData,
}),
};
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,15 @@ export const LION_IDLE_STATE: State<LionEntity, LionIdleStateData> = {
entity.targetDirection = Math.atan2(dy, dx);
}

// Stay idle
entity.acceleration = 0;

if (entity.actions.ambush.enabled) {
return {
nextState: 'LION_AMBUSH',
data: { enteredAt: context.updateContext.gameState.time },
};
}

return { nextState: 'LION_IDLE', data };
},

onEnter: (context) => ({
enteredAt: context.updateContext.gameState.time,
}),
};
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,4 @@ export const LION_MOVING_TO_TARGET_STATE: State<LionEntity, LionMovingStateData>
},
};
},

onEnter: (context) => ({
enteredAt: context.updateContext.gameState.time,
}),
};
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,4 @@ export const PREY_IDLE_STATE: State<PreyEntity, PreyIdleStateData> = {
entity.acceleration = 0;
return { nextState: 'PREY_IDLE', data };
},

onEnter: (context) => ({
enteredAt: context.updateContext.gameState.time,
}),
};
Loading

0 comments on commit 7767452

Please sign in to comment.