diff --git a/src/features/ui/components/episode-list-item/episode-list-item.module.css b/src/features/ui/components/episode-list-item/episode-list-item.module.css
new file mode 100644
index 0000000..8501ad8
--- /dev/null
+++ b/src/features/ui/components/episode-list-item/episode-list-item.module.css
@@ -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;
+}
diff --git a/src/features/ui/components/episode-list-item/episode-list-item.tsx b/src/features/ui/components/episode-list-item/episode-list-item.tsx
new file mode 100644
index 0000000..dea17d9
--- /dev/null
+++ b/src/features/ui/components/episode-list-item/episode-list-item.tsx
@@ -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 (
+
+ );
+};
diff --git a/src/features/ui/components/episode-list-item/index.ts b/src/features/ui/components/episode-list-item/index.ts
new file mode 100644
index 0000000..ed78850
--- /dev/null
+++ b/src/features/ui/components/episode-list-item/index.ts
@@ -0,0 +1 @@
+export * from './episode-list-item';
diff --git a/src/features/ui/components/episodes-view/episodes-view.module.css b/src/features/ui/components/episodes-view/episodes-view.module.css
new file mode 100644
index 0000000..c7890a8
--- /dev/null
+++ b/src/features/ui/components/episodes-view/episodes-view.module.css
@@ -0,0 +1,12 @@
+.container {
+ overflow-y: scroll;
+ overflow-x: hidden;
+ max-height: 100%;
+}
+
+.episodes {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ padding: 2rem;
+}
diff --git a/src/features/ui/components/episodes-view/episodes-view.tsx b/src/features/ui/components/episodes-view/episodes-view.tsx
new file mode 100644
index 0000000..4c74568
--- /dev/null
+++ b/src/features/ui/components/episodes-view/episodes-view.tsx
@@ -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
Loading...
;
+ }
+
+ return (
+
+
+ {episodes.map((episode) => (
+
+ ))}
+
+
+ );
+};
diff --git a/src/features/ui/components/episodes-view/index.ts b/src/features/ui/components/episodes-view/index.ts
new file mode 100644
index 0000000..dc9f5dc
--- /dev/null
+++ b/src/features/ui/components/episodes-view/index.ts
@@ -0,0 +1 @@
+export * from './episodes-view';
diff --git a/src/features/ui/components/game-view/game-view.tsx b/src/features/ui/components/game-view/game-view.tsx
new file mode 100644
index 0000000..cd8a47a
--- /dev/null
+++ b/src/features/ui/components/game-view/game-view.tsx
@@ -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 (
+ <>
+
+
+ >
+ );
+};
diff --git a/src/features/ui/components/game-view/index.ts b/src/features/ui/components/game-view/index.ts
new file mode 100644
index 0000000..23a8a8f
--- /dev/null
+++ b/src/features/ui/components/game-view/index.ts
@@ -0,0 +1 @@
+export * from './game-view';
diff --git a/src/features/ui/components/level-list-item/index.ts b/src/features/ui/components/level-list-item/index.ts
new file mode 100644
index 0000000..158a5fa
--- /dev/null
+++ b/src/features/ui/components/level-list-item/index.ts
@@ -0,0 +1 @@
+export * from './level-list-item';
diff --git a/src/features/ui/components/level-list-item/level-list-item.module.css b/src/features/ui/components/level-list-item/level-list-item.module.css
new file mode 100644
index 0000000..eebb507
--- /dev/null
+++ b/src/features/ui/components/level-list-item/level-list-item.module.css
@@ -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;
+}
diff --git a/src/features/ui/components/level-list-item/level-list-item.tsx b/src/features/ui/components/level-list-item/level-list-item.tsx
new file mode 100644
index 0000000..62ac76b
--- /dev/null
+++ b/src/features/ui/components/level-list-item/level-list-item.tsx
@@ -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 (
+
+ );
+};
+
+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 (
+
+
+ {entities.map((entity) => (
+
+ ))}
+
+
+ );
+});
+Preview.displayName = 'Preview';
diff --git a/src/features/ui/components/levels-view/index.ts b/src/features/ui/components/levels-view/index.ts
new file mode 100644
index 0000000..21c4775
--- /dev/null
+++ b/src/features/ui/components/levels-view/index.ts
@@ -0,0 +1 @@
+export * from './levels-view';
diff --git a/src/features/ui/components/levels-view/levels-view.module.css b/src/features/ui/components/levels-view/levels-view.module.css
new file mode 100644
index 0000000..e7b0178
--- /dev/null
+++ b/src/features/ui/components/levels-view/levels-view.module.css
@@ -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;
+}
diff --git a/src/features/ui/components/levels-view/levels-view.tsx b/src/features/ui/components/levels-view/levels-view.tsx
new file mode 100644
index 0000000..09ded45
--- /dev/null
+++ b/src/features/ui/components/levels-view/levels-view.tsx
@@ -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 Loading...
;
+ }
+
+ return (
+
+
+ {levels.map((level) => (
+
+ ))}
+
+
+ );
+};
diff --git a/src/game.tsx b/src/game.tsx
index 4564cb7..9c28eb9 100644
--- a/src/game.tsx
+++ b/src/game.tsx
@@ -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 ;
+ }
- const { episodes } = useEpisodes();
+ if (!level) {
+ return ;
+ }
- return (
- <>
- {JSON.stringify(episodes, null, 2)}
-
-
- >
- );
+ return <>{Boolean(level) && }>;
};
export default Game;
diff --git a/src/global.css b/src/global.css
index 345ed34..0eef387 100644
--- a/src/global.css
+++ b/src/global.css
@@ -1,6 +1,6 @@
#root {
- max-width: 1280px;
- margin: 0 auto;
- padding: 2rem;
- text-align: center;
+ height: 100vh;
+ width: 100%;
+ overflow: hidden;
+ position: relative;
}