Skip to content

Commit

Permalink
enhance development features; add DevConfigPanel, conditional renderi…
Browse files Browse the repository at this point in the history
…ng for landscape elements, and AI Santa management based on dev configuration
  • Loading branch information
gtanczyk committed Dec 1, 2024
1 parent dccd84e commit ac9bd57
Show file tree
Hide file tree
Showing 7 changed files with 400 additions and 40 deletions.
156 changes: 156 additions & 0 deletions games/xmas/src/screens/play/dev/dev-config-panel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import 'react';
import styled from 'styled-components';
import { useDevConfig, useDevMode } from './dev-config';

export function DevConfigPanel() {
const isDevMode = useDevMode();
const [config, updateConfig] = useDevConfig();

if (!isDevMode) {
return null;
}

const handleToggle = (key: keyof typeof config) => {
updateConfig({ [key]: !config[key] });
};

return (
<Panel>
<Title>Dev Configuration</Title>

<Section>
<SectionTitle>Game Features</SectionTitle>
<ToggleContainer>
<ToggleInput
type="checkbox"
checked={config.enableAISantas}
onChange={() => handleToggle('enableAISantas')}
/>
AI Santas
</ToggleContainer>
</Section>

<Section>
<SectionTitle>Rendering</SectionTitle>
<ToggleContainer>
<ToggleInput type="checkbox" checked={config.renderSnow} onChange={() => handleToggle('renderSnow')} />
Snow Effects
</ToggleContainer>
<ToggleContainer>
<ToggleInput type="checkbox" checked={config.renderFire} onChange={() => handleToggle('renderFire')} />
Fire Effects
</ToggleContainer>
<ToggleContainer>
<ToggleInput type="checkbox" checked={config.renderTrees} onChange={() => handleToggle('renderTrees')} />
Trees
</ToggleContainer>
<ToggleContainer>
<ToggleInput
type="checkbox"
checked={config.renderMountains}
onChange={() => handleToggle('renderMountains')}
/>
Mountains
</ToggleContainer>
<ToggleContainer>
<ToggleInput
type="checkbox"
checked={config.renderSnowGround}
onChange={() => handleToggle('renderSnowGround')}
/>
Snow Ground
</ToggleContainer>
</Section>
</Panel>
);
}

const Panel = styled.div`
position: fixed;
top: 10px;
right: 10px;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 15px;
border-radius: 8px;
min-width: 200px;
z-index: 1000;
font-family: 'Arial', sans-serif;
backdrop-filter: blur(5px);
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
`;

const Title = styled.h3`
margin: 0 0 15px 0;
font-size: 14px;
text-transform: uppercase;
letter-spacing: 1px;
color: #fff;
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
padding-bottom: 8px;
`;

const Section = styled.div`
margin-bottom: 15px;
&:last-child {
margin-bottom: 0;
}
`;

const SectionTitle = styled.h4`
margin: 0 0 8px 0;
font-size: 12px;
color: rgba(255, 255, 255, 0.7);
font-weight: normal;
`;

const ToggleContainer = styled.label`
display: flex;
align-items: center;
margin-bottom: 8px;
cursor: pointer;
font-size: 12px;
&:last-child {
margin-bottom: 0;
}
&:hover {
color: #4a9eff;
}
`;

const ToggleInput = styled.input`
margin-right: 8px;
cursor: pointer;
/* Custom checkbox styling */
appearance: none;
width: 16px;
height: 16px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 3px;
background: transparent;
position: relative;
&:checked {
background: #4a9eff;
border-color: #4a9eff;
}
&:checked::after {
content: '✓';
position: absolute;
color: white;
font-size: 12px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
&:hover {
border-color: #4a9eff;
}
`;
153 changes: 153 additions & 0 deletions games/xmas/src/screens/play/dev/dev-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { useEffect, useState } from 'react';

/**
* Interface defining the development configuration options
*/
export interface DevConfig {
// AI Features
enableAISantas: boolean;

// Rendering Features
renderSnow: boolean;
renderFire: boolean;
renderTrees: boolean;
renderMountains: boolean;
renderSnowGround: boolean;
}

/**
* Default configuration values
*/
const DEFAULT_CONFIG: DevConfig = {
enableAISantas: true,
renderSnow: true,
renderFire: true,
renderTrees: true,
renderMountains: true,
renderSnowGround: true,
};

/**
* Custom event for config updates
*/
const CONFIG_UPDATE_EVENT = 'devConfigUpdate';

/**
* Event interface for config updates
*/
interface DevConfigUpdateEvent extends CustomEvent {
detail: Partial<DevConfig>;
}

/**
* Class managing the development configuration
*/
class DevConfigManager {
private config: DevConfig;
private listeners: Set<(config: DevConfig) => void>;

constructor() {
this.config = { ...DEFAULT_CONFIG };
this.listeners = new Set();
this.initHashListener();
}

/**
* Initialize hash change listener for dev mode
*/
private initHashListener() {
window.addEventListener('hashchange', this.checkDevMode.bind(this));
this.checkDevMode(); // Initial check
}

/**
* Check if dev mode is enabled via URL hash
*/
private checkDevMode() {
const isDevMode = window.location.hash.includes('#dev');
if (!isDevMode) {
// Reset to default config when dev mode is disabled
this.updateConfig(DEFAULT_CONFIG);
}
}

/**
* Get current configuration state
*/
getConfig(): Readonly<DevConfig> {
return { ...this.config };
}

/**
* Update configuration
*/
updateConfig(updates: Partial<DevConfig>) {
this.config = {
...this.config,
...updates,
};

// Notify all listeners
this.listeners.forEach((listener) => listener(this.getConfig()));

// Dispatch custom event
const event = new CustomEvent(CONFIG_UPDATE_EVENT, {
detail: updates,
}) as DevConfigUpdateEvent;
window.dispatchEvent(event);
}

/**
* Subscribe to configuration changes
*/
subscribe(listener: (config: DevConfig) => void): () => void {
this.listeners.add(listener);
return () => this.listeners.delete(listener);
}

/**
* Check if dev mode is currently active
*/
isDevMode(): boolean {
return window.location.hash.includes('#dev');
}
}

// Create singleton instance
export const devConfig = new DevConfigManager();

/**
* React hook for accessing dev configuration
*/
export function useDevConfig(): [DevConfig, (updates: Partial<DevConfig>) => void] {
const [config, setConfig] = useState<DevConfig>(devConfig.getConfig());

useEffect(() => {
// Subscribe to config updates
const unsubscribe = devConfig.subscribe((newConfig) => {
setConfig(newConfig);
});

return unsubscribe;
}, []);

return [config, (updates) => devConfig.updateConfig(updates)];
}

/**
* React hook for checking dev mode status
*/
export function useDevMode(): boolean {
const [isDevMode, setIsDevMode] = useState(devConfig.isDevMode());

useEffect(() => {
const handleHashChange = () => {
setIsDevMode(devConfig.isDevMode());
};

window.addEventListener('hashchange', handleHashChange);
return () => window.removeEventListener('hashchange', handleHashChange);
}, []);

return isDevMode;
}
7 changes: 7 additions & 0 deletions games/xmas/src/screens/play/game-ai/ai-santa-spawner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createSanta } from '../game-world/game-world-manipulate';
import { AI_CONFIG, AI_DIFFICULTY, AISanta, WaveState } from './ai-santa-types';
import { initializeAIState } from './ai-santa-decision';
import { GAME_WORLD_HEIGHT, GAME_WORLD_WIDTH } from '../game-world/game-world-consts';
import { devConfig } from '../dev/dev-config';

/**
* Calculate a safe spawn position for AI Santa
Expand Down Expand Up @@ -93,6 +94,12 @@ function spawnWaveSantas(gameState: GameWorldState, waveState: WaveState): void
* Main update function for AI Santa spawning system
*/
export function updateAISpawner(gameState: GameWorldState, waveState: WaveState): void {
if (!devConfig.getConfig().enableAISantas) {
// Reset wave state when AI is disabled
waveState.currentWave = AI_DIFFICULTY.WAVE_1;
return;
}

// Check if current wave is completed
if (areAllSantasEliminated(gameState)) {
if (waveState.nextSpawnTime === null) {
Expand Down
26 changes: 20 additions & 6 deletions games/xmas/src/screens/play/game-render/game-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { renderSanta } from './santa-renderer';
import { renderFireballs } from './fireball-renderer';
import { renderSky } from './landscape/sky/sky-renderer';
import { renderGifts } from './gift-renderer';
import { devConfig } from '../dev/dev-config';

export const renderGame = (ctx: CanvasRenderingContext2D, world: GameWorldState, renderState: RenderState) => {
const { viewport } = renderState;
Expand All @@ -19,20 +20,33 @@ export const renderGame = (ctx: CanvasRenderingContext2D, world: GameWorldState,
// Apply viewport translation
ctx.translate(viewport.x, viewport.y);

// Render all game elements with the applied translation
renderLandscape(ctx, renderState.landscape, renderState.viewport);
renderSnow(ctx, renderState);
// Get current dev configuration
const config = devConfig.getConfig();

// Conditionally render landscape elements based on dev config
if (config.renderMountains || config.renderTrees || config.renderSnowGround) {
// Pass dev config to control individual landscape elements
renderLandscape(ctx, renderState.landscape, renderState.viewport);
}

// Conditionally render snow effects
if (config.renderSnow) {
renderSnow(ctx, renderState);
}

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

// Render all santas
// Always render Santas as they're essential game elements
world.santas.forEach((santa) => {
renderSanta(ctx, santa, world.time);
});

renderFireballs(ctx, renderState);
// Conditionally render fire effects
if (config.renderFire) {
renderFireballs(ctx, renderState);
}

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

0 comments on commit ac9bd57

Please sign in to comment.