Skip to content

Commit

Permalink
feat(ui): basic episodes and level selection
Browse files Browse the repository at this point in the history
  • Loading branch information
wialy committed Mar 19, 2024
1 parent 2a7747f commit edd5490
Show file tree
Hide file tree
Showing 16 changed files with 258 additions and 36 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.container {
display: flex;
background-color: white;
flex-direction: column;
padding: 1rem;
border-radius: 0.5rem;
text-align: start;
appearance: none;
border: none;
cursor: pointer;
}

.name {
font-weight: bold;
}

.description {
font-size: 1rem;
}
17 changes: 17 additions & 0 deletions src/features/ui/components/episode-list-item/episode-list-item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { EpisodeRecord } from '../../../editor/types';
import { useGameState } from '../../../game/hooks/use-game-state';
import $$ from './episode-list-item.module.css';

export const EpisodeListItem = ({ levels, name, symbols }: EpisodeRecord) => {
const { setEpisode } = useGameState();

return (
<button
className={$$.container}
onClick={() => setEpisode(symbols)}
>
<div className={$$.name}>{name}</div>
<div className={$$.description}>{levels} levels</div>
</button>
);
};
1 change: 1 addition & 0 deletions src/features/ui/components/episode-list-item/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './episode-list-item';
12 changes: 12 additions & 0 deletions src/features/ui/components/episodes-view/episodes-view.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.container {
overflow-y: scroll;
overflow-x: hidden;
max-height: 100%;
}

.episodes {
display: flex;
flex-direction: column;
gap: 1rem;
padding: 2rem;
}
24 changes: 24 additions & 0 deletions src/features/ui/components/episodes-view/episodes-view.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useEpisodes } from '../../../editor/hooks/use-episodes';
import { EpisodeListItem } from '../episode-list-item';
import $$ from './episodes-view.module.css';

export const EpisodesView = () => {
const { episodes, isLoading } = useEpisodes();

if (isLoading) {
return <div>Loading...</div>;
}

return (
<div className={$$.container}>
<div className={$$.episodes}>
{episodes.map((episode) => (
<EpisodeListItem
{...episode}
key={episode.symbols}
/>
))}
</div>
</div>
);
};
1 change: 1 addition & 0 deletions src/features/ui/components/episodes-view/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './episodes-view';
27 changes: 27 additions & 0 deletions src/features/ui/components/game-view/game-view.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { useMemo } from 'react';

import { getIdEntities } from '../../../editor/utils/get-id-entities';
import { useControls } from '../../../game/hooks/use-controls';
import { useGame } from '../../../game/hooks/use-game';
import { useGameState } from '../../../game/hooks/use-game-state';
import { BoardView } from '../board-view';
import { SwipeArea } from '../swipe-area';

export const GameView = () => {
const { level } = useGameState();

const levelEntities = useMemo(() => getIdEntities(level), [level]);

const { entities, setEntities } = useGame({
level: { entities: levelEntities, id: '' },
});

const { swipeProps } = useControls({ setEntities });

return (
<>
<BoardView board={{ entities }} />
<SwipeArea {...swipeProps} />
</>
);
};
1 change: 1 addition & 0 deletions src/features/ui/components/game-view/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './game-view';
1 change: 1 addition & 0 deletions src/features/ui/components/level-list-item/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './level-list-item';
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.container {
display: flex;
border-radius: 1rem;
background-color: white;
appearance: none;
border: none;
cursor: pointer;
flex-direction: column;
align-items: center;
padding: 0;
overflow: hidden;
z-index: 1;
outline: none;
}

.info {
padding: 1rem;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
gap: 0.5rem;
}

.name {
font-weight: bold;
white-space: nowrap;
}
72 changes: 72 additions & 0 deletions src/features/ui/components/level-list-item/level-list-item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { memo } from 'react';

import { MAX_GRID_HEIGHT, MAX_GRID_WIDTH } from '../../../editor/constants';
import { LevelRecord } from '../../../editor/types';
import { getIdEntities } from '../../../editor/utils/get-id-entities';
import { getBounds } from '../../../engine/utils/get-bounds';
import { useGameState } from '../../../game/hooks/use-game-state';
import { TILE_SIZE } from '../../constants';
import { EntityView } from '../entity-view';
import $$ from './level-list-item.module.css';

export const LevelListItem = ({
level: { id, name, steps },
}: {
level: LevelRecord;
}) => {
const { setLevel } = useGameState();

return (
<button
className={$$.container}
onClick={() => setLevel(id)}
>
<Preview id={id} />
<div className={$$.info}>
<div className={$$.name}>{name}</div>
<div>🏆 {steps}</div>
</div>
</button>
);
};

const SCALE = 0.5;

const Preview = memo(({ id }: { id: string }) => {
const entities = getIdEntities(id);
const { bottomRight, topLeft } = getBounds({ entities });
const width = bottomRight.x - topLeft.x;
const height = bottomRight.y - topLeft.y;

return (
<div
style={{
alignItems: 'center',
backgroundColor: '#232323',
boxSizing: 'border-box',
display: 'flex',
height: TILE_SIZE * MAX_GRID_HEIGHT * SCALE,
justifyContent: 'center',
pointerEvents: 'none',
width: TILE_SIZE * (MAX_GRID_WIDTH + 3) * SCALE,
}}
>
<pre
style={{
height: height * TILE_SIZE * SCALE,
position: 'relative',
width: width * TILE_SIZE * SCALE,
}}
>
{entities.map((entity) => (
<EntityView
key={entity.id}
entity={entity}
scale={SCALE}
/>
))}
</pre>
</div>
);
});
Preview.displayName = 'Preview';
1 change: 1 addition & 0 deletions src/features/ui/components/levels-view/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './levels-view';
13 changes: 13 additions & 0 deletions src/features/ui/components/levels-view/levels-view.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.container {
overflow-x: hidden;
overflow-y: scroll;
max-height: 100%;
}

.levels {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 1rem;
padding: 1rem;
}
25 changes: 25 additions & 0 deletions src/features/ui/components/levels-view/levels-view.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useEpisodeLevels } from '../../../editor/hooks/use-episode-levels';
import { getParsedLevelRecord } from '../../../editor/utils/get-parsed-level-record';
import { LevelListItem } from '../level-list-item';
import $$ from './levels-view.module.css';

export const LevelsView = () => {
const { isLoading, levels } = useEpisodeLevels();

if (isLoading) {
return <div>Loading...</div>;
}

return (
<div className={$$.container}>
<div className={$$.levels}>
{levels.map((level) => (
<LevelListItem
key={level}
level={getParsedLevelRecord(level)}
/>
))}
</div>
</div>
);
};
44 changes: 12 additions & 32 deletions src/game.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,22 @@
import './global.css';

import { useEpisodes } from './features/editor/hooks/use-episodes';
import { VECTOR_LEFT } from './features/engine/constants';
import { Entity } from './features/engine/types/entities';
import { createEntity } from './features/engine/utils/create-entity';
import { useControls } from './features/game/hooks/use-controls';
import { useGame } from './features/game/hooks/use-game';
import { BoardView } from './features/ui/components/board-view';
import { SwipeArea } from './features/ui/components/swipe-area';

const level: Entity[] = [
createEntity('floor', { position: { x: 2, y: 0 } }),
createEntity('floor', { position: { x: 2, y: 1 } }),
createEntity('floor', { position: { x: 1, y: 0 } }),
createEntity('floor', { position: { x: 1, y: 1 } }),
createEntity('floor', { direction: VECTOR_LEFT, position: { x: 2, y: 2 } }),
createEntity('floor', { position: { x: 1, y: 2 } }),
createEntity('floor', { position: { x: 0, y: 2 }, target: 1 }),
createEntity('dice', { position: { x: 2, y: 0 }, value: 0 }),
createEntity('dice', { position: { x: 1, y: 1 } }),
];
import { useGameState } from './features/game/hooks/use-game-state';
import { EpisodesView } from './features/ui/components/episodes-view';
import { GameView } from './features/ui/components/game-view';
import { LevelsView } from './features/ui/components/levels-view';

const Game = () => {
const { entities, setEntities } = useGame({
level: { entities: level, id: '' },
});
const { episode, level } = useGameState();

const { swipeProps } = useControls({ setEntities });
if (!episode) {
return <EpisodesView />;
}

const { episodes } = useEpisodes();
if (!level) {
return <LevelsView />;
}

return (
<>
<pre>{JSON.stringify(episodes, null, 2)}</pre>
<BoardView board={{ entities }} />
<SwipeArea {...swipeProps} />
</>
);
return <>{Boolean(level) && <GameView />}</>;
};

export default Game;
8 changes: 4 additions & 4 deletions src/global.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
height: 100vh;
width: 100%;
overflow: hidden;
position: relative;
}

0 comments on commit edd5490

Please sign in to comment.