diff --git a/client/src/components/landing_page.tsx b/client/src/components/landing_page.tsx
index 0427eae..08c1e75 100644
--- a/client/src/components/landing_page.tsx
+++ b/client/src/components/landing_page.tsx
@@ -26,7 +26,6 @@ function GithubIcon({url='https://github.com'}) {
return
diff --git a/client/src/components/welcome.tsx b/client/src/components/welcome.tsx
index 1d13fbb..ab4c120 100644
--- a/client/src/components/welcome.tsx
+++ b/client/src/components/welcome.tsx
@@ -18,26 +18,26 @@ import WorldSelectionMenu from './world_selection_menu';
import {PrivacyPolicy} from './privacy_policy';
import { Button } from './button';
import { Documentation, Inventory } from './inventory';
+import { store } from '../state/store';
cytoscape.use( klay );
-const N = 24 // max number of levels per world
+const N = 18 // max number of levels per world
const R = 800 // radius of a world
-const r = 110 // radius of a level
-const s = 100 // global scale
+const r = 160 // radius of a level
+const s = 120 // global scale
const padding = 2000 // padding of the graphic (on a different scale)
-function LevelIcon({ worldId, levelId, position }) {
+function LevelIcon({ worldId, levelId, position, completed, available }) {
const gameId = React.useContext(GameIdContext)
- const completed = useSelector(selectCompleted(gameId, worldId,levelId))
- const x = s * position.x + Math.sin(levelId * 2 * Math.PI / N) * (R + 1.2*r + 2*Math.floor((levelId - 1)/N))
- const y = s * position.y - Math.cos(levelId * 2 * Math.PI / N) * (R + 1.2*r + 2*Math.floor((levelId - 1)/N))
+ const x = s * position.x + Math.sin(levelId * 2 * Math.PI / N) * (R + 1.2*r + 2.4*r*Math.floor((levelId - 1)/N))
+ const y = s * position.y - Math.cos(levelId * 2 * Math.PI / N) * (R + 1.2*r + 2.4*r*Math.floor((levelId - 1)/N))
// TODO: relative positioning?
return (
-
+
)
}
@@ -72,35 +72,64 @@ function Welcome() {
const svgElements = []
+ // For each `worldId` as index, this contains a list of booleans with indices
+ // 0, 1, …, n. Index `0` will be set to `false` if any dependency is not completely solved.
+ // Indices `1, …, n` indicate if the corresponding level is completed
+ var completed = {}
+
if (gameInfo.data) {
+
+ // Fill `completed` with the level data.
+ for (let worldId in nodes) {
+ let position: cytoscape.Position = nodes[worldId].position
+ let state = store.getState()
+
+ completed[worldId] = Array.from({ length: gameInfo.data.worldSize[worldId] + 1 }, (_, i) => {
+ // Index `0` might be set to `false` in the loop over the edges
+ if (!i) {return true}
+ return selectCompleted(gameId, worldId, i)(state)
+ })
+ }
+
for (let i in gameInfo.data.worlds.edges) {
const edge = gameInfo.data.worlds.edges[i]
+
+ // If the origin world is not completed, mark the target world as non-playable
+ let unlocked = completed[edge[0]].slice(1).every(Boolean)
+ if (!unlocked) {completed[edge[1]][0] = false}
+
+ // Draw the connection edges
svgElements.push(
+ x2={s*nodes[edge[1]].position.x} y2={s*nodes[edge[1]].position.y}
+ stroke={unlocked ? "green" : "#bbb"} strokeWidth={s}/>
)
}
- for (let id in nodes) {
- let position: cytoscape.Position = nodes[id].position
-
- for (let i = 1; i <= gameInfo.data.worldSize[id]; i++) {
+ for (let worldId in nodes) {
+ // Draw the level bubbles
+ let position: cytoscape.Position = nodes[worldId].position
+ for (let i = 1; i <= gameInfo.data.worldSize[worldId]; i++) {
svgElements.push(
+ key={`/${gameId}/world/${worldId}/level/${i}`}
+ position={position} worldId={worldId} levelId={i}
+ completed={completed[worldId][i]} available={completed[worldId][i-1]}/>
)
}
+ // Draw the worlds
+ let worldUnlocked = completed[worldId][0]
+ let worldCompleted = completed[worldId].slice(1).every(Boolean)
svgElements.push(
-
+
+ fill={worldCompleted ? "green" : worldUnlocked ? "#1976d2": "#999"}/>