Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Web Dev Bootcamp spring 2024, week 11 - Project Labyrinth - Sofia #4

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 23 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,40 @@

# Labyrinth - Zustand Project

Replace this readme with your own information about your project.
### The Problem

Start by briefly describing the assignment in a sentence or two. Keep it short and to the point.
This week we're practicing using Zustand by creating a classic text-based adventure game from the Technigo API. The storyline is done; all we have to do is create the logic and style.

## Getting Started with the Project
I found it pretty easy to get the basics down. The app is working now, from what I can tell. I wish I could find a way to tell if the username is new or already in use, so that the user would know to find another... After some input fom coaches, I instead added a random userId to ensure the username is always unique.

### Dependency Installation & Startup Development Server
I'm having some issues with the fetch running twice intially, and some states updating when they shouldn't.
I'm happy I found an easy way to put data in storage.

Once cloned, navigate to the project's root directory and this project uses npm (Node Package Manager) to manage its dependencies.
There are some issues with how game history is set - there are way too many calls.

The command below is a combination of installing dependencies, opening up the project on VS Code and it will run a development server on your terminal.
Found a fun typing animation package!

```bash
npm i && code . && npm run dev
```
#### Requirements

### The Problem
- Your Labyrinth game should guide the user with instructions about the game and moves to make
- Use Zustand to store the current state of the game
- Perform asynchronous operations in the app store
- Focus on making the UX of your app good. Handle the response delay
- Don't forget CSS! Your Labyrinth should be well-styled

#### Next

Describe how you approached to problem, and what tools and techniques you used to solve it. How did you plan? What technologies did you use? If you had more time, what would be next?
- If I can find pixel arrows, I will add those to indicate direction.

- Trying to find a way to load the history separate, so it looks like only the newest is added. Don't know how...

- I would like to do the classic look, with the game playing out in "the terminal". So it's going to take some styling, as well as some added functionality. For the real authentic feel, the user should have to type the commands aswell... We'll see if I have time.

### View it live

Every project should be deployed somewhere. Be sure to include the link to the deployed project so that the viewer can click around and see what it's all about.
[https://sofias-labyrinth.netlify.app/]

[![Netlify Status](https://api.netlify.com/api/v1/badges/8bce6579-38bb-4437-a9f1-97e9cd1a8e1c/deploy-status)](https://app.netlify.com/sites/sofias-labyrinth/deploys)

## Instructions

Expand Down
14 changes: 10 additions & 4 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Project Labyrinth Zustand</title>
<link
rel="icon"
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🐻</text></svg>" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0" />
<title>Sofias Labyrinth Adventure 🧙‍♀️</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
<script
type="module"
src="/src/main.jsx"></script>
</body>
</html>
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-type-animation": "^3.2.0",
"zustand": "^4.4.1"
},
"devDependencies": {
Expand Down
20 changes: 17 additions & 3 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
import { useEffect } from "react";
import { useStore } from "./stores/useStore";
import { Situation } from "./components/Situation";
import { Input } from "./components/Input";
import { Story } from "./components/Story";

export const App = () => {
const fetch = useStore(state => state.fetch);
const userName = useStore(state => state.userName);

useEffect(() => {
userName && setTimeout(fetch(), 5000);
}, [userName]);

return (
<div>
Labyrinth Project
</div>
<div>
<h1>Labyrinth Project</h1>
<Story />
{!userName ? <Input /> : <Situation />}
</div>
);
};
Binary file added src/assets/RubikScribble-Regular.ttf
Binary file not shown.
Binary file added src/assets/Silkscreen-Bold.ttf
Binary file not shown.
Binary file added src/assets/Silkscreen-Regular.ttf
Binary file not shown.
34 changes: 34 additions & 0 deletions src/components/Action.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Button } from "./Button";
import "../css/action.css";
import { useEffect, useRef } from "react";

export const Action = ({ action }) => {
const detailsRef = useRef();

useEffect(() => {
detailsRef.current.addEventListener(
"toggle",
() =>
detailsRef.current.open &&
detailsRef.current.scrollIntoView({
behavior: "smooth",
block: "center",
})
);
});
sofia32057 marked this conversation as resolved.
Show resolved Hide resolved

return (
<details
ref={detailsRef}
className="situation-details"
name="situation-details">
<summary>{`Look ${action.direction}`}</summary>
sofia32057 marked this conversation as resolved.
Show resolved Hide resolved
{action.description}
<Button
key={`m-${action.direction}`}
direction={action.direction}
action={action.description}
/>
</details>
);
};
27 changes: 27 additions & 0 deletions src/components/Button.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { useStore } from "../stores/useStore";
import "../css/button.css";

export const Button = ({ direction, action }) => {
const { fetchAction, setHistory } = useStore();
const restart = useStore(state => state.restart);
const setLoading = useStore(state => state.setLoading);

const handleClick = event => {
setLoading();
if (direction === "home") {
restart();
} else {
fetchAction(event.target.value);
setHistory({ action: action, move: direction });
}
};

return (
<button
className="btn move"
onClick={handleClick}
value={direction}>
{`Go ${direction}`}
</button>
);
};
35 changes: 35 additions & 0 deletions src/components/Input.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useState } from "react";
import { useStore } from "../stores/useStore";
import "../css/input.css";

export const Input = () => {
const { setUserName, setUserId } = useStore();
const [newName, setNewName] = useState("");

const handleSubmit = event => {
event.preventDefault();
setUserId();
setUserName(newName);
setNewName("");
};

const handleChange = event => setNewName(event.target.value);

return (
<div className="form-container">
<p>Enter a username to play</p>
<form
name="name-form"
onSubmit={handleSubmit}>
<input
name="name-input"
type="text"
value={newName}
onChange={handleChange}
placeholder="Type username"
/>
<button className="btn">Start game</button>
</form>
</div>
);
};
56 changes: 56 additions & 0 deletions src/components/Situation.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { useStore } from "../stores/useStore";
import { Action } from "./Action";
import "../css/situation.css";
import { Button } from "./Button";
import { useEffect, useRef } from "react";
import { TypeAnimation } from "react-type-animation";
sofia32057 marked this conversation as resolved.
Show resolved Hide resolved

export const Situation = () => {
const { data, loading } = useStore();
const userName = useStore(state => state.userName);
const container = useRef();

useEffect(() => {
container.current.scrollIntoView({ behavior: "smooth", block: "center" });
}, [loading]);

return (
<section
className="situation-container"
ref={container}>
{/* If status is loading, show message */}
{loading ? (
<p className="loading">Adventure loading..........</p>
) : (
// otherwise, show situation
<>
{data.coordinates === "0,0" && (
<p>Hello {userName}! Let&apos;s start from the beginning!</p>
)}
<p className="situation-desc">{data.description}</p>
<TypeAnimation
sequence={[`Where do you want to go next ${userName}?`]}
wrapper="span"
className="prompt"
speed={30}
repeat={1}
/>
<div className="actions-container">
{data.actions.length > 0 ? (
data.actions.map(opt => {
return (
<Action
key={`a-${opt.direction}`}
action={opt}
/>
);
})
) : (
<Button direction="home" />
)}
</div>
</>
)}
</section>
);
};
24 changes: 24 additions & 0 deletions src/components/Story.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useStore } from "../stores/useStore";
import "../css/story.css";

export const Story = () => {
const { gameHistory } = useStore();

return (
<section className="game-history">
{gameHistory.length > 0 &&
gameHistory.map(step => {
return (
step.move && (
<div
key={step._id}
id={step._id}>
<p>{step.scene}</p>
<p>{`${step.action} --> ${step.move}`}</p>
</div>
)
);
})}
</section>
);
};
12 changes: 12 additions & 0 deletions src/css/action.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.situation-details > * {
margin: 1rem auto;
}

.situation-details > summary {
font-family: "Silkscreen bold";
cursor: pointer;
}

.situation-details > summary:focus {
outline-color: var(--primary-color);
}
27 changes: 27 additions & 0 deletions src/css/button.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.btn {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #888;
cursor: pointer;
transition: border-color 0.25s ease-in-out;
color: black;
}

.move {
display: block;
}

.btn:hover {
border-color: white;
color: white;
background-color: var(--primary-color);
}

.btn:focus,
.btn:focus-visible {
outline: 4px auto var(--primary-color);
}
23 changes: 23 additions & 0 deletions src/css/input.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
form {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem
}

input {
font-family: inherit;
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
max-width: 200px;
text-wrap: wrap;
}

@media all and (min-width: 668px) {
form {
flex-direction: row;
justify-content: center;
}
}
29 changes: 29 additions & 0 deletions src/css/situation.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.situation-container {
display: flex;
flex-direction: column;
padding-bottom: 10vh;
}

.prompt {
color: var(--primary-color);
}

.actions-container {
display: flex;
flex-direction: column;
align-items: center;
}

.actions-container > * {
margin: 1rem;
}

.loading {
width: fit-content;
animation: typewriter 6s 1s 1 normal both,
blinkTextCursor 0.5s infinite;
overflow: hidden;
white-space: nowrap;
text-align: left;
}

Loading